gsk: Move all gskgl* files into gl/
authorTimm Bäder <mail@baedert.org>
Wed, 22 Nov 2017 19:13:46 +0000 (20:13 +0100)
committerTimm Bäder <mail@baedert.org>
Thu, 21 Dec 2017 18:12:30 +0000 (19:12 +0100)
22 files changed:
gsk/gl/gskgldriver.c [new file with mode: 0644]
gsk/gl/gskgldriverprivate.h [new file with mode: 0644]
gsk/gl/gskglglyphcache.c [new file with mode: 0644]
gsk/gl/gskglglyphcacheprivate.h [new file with mode: 0644]
gsk/gl/gskglimage.c [new file with mode: 0644]
gsk/gl/gskglimageprivate.h [new file with mode: 0644]
gsk/gl/gskglprofiler.c [new file with mode: 0644]
gsk/gl/gskglprofilerprivate.h [new file with mode: 0644]
gsk/gl/gskglrenderer.c [new file with mode: 0644]
gsk/gl/gskglrendererprivate.h [new file with mode: 0644]
gsk/gskgldriver.c [deleted file]
gsk/gskgldriverprivate.h [deleted file]
gsk/gskglglyphcache.c [deleted file]
gsk/gskglglyphcacheprivate.h [deleted file]
gsk/gskglimage.c [deleted file]
gsk/gskglimageprivate.h [deleted file]
gsk/gskglprofiler.c [deleted file]
gsk/gskglprofilerprivate.h [deleted file]
gsk/gskglrenderer.c [deleted file]
gsk/gskglrendererprivate.h [deleted file]
gsk/gskrenderer.c
gsk/meson.build

diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c
new file mode 100644 (file)
index 0000000..9b49ebe
--- /dev/null
@@ -0,0 +1,950 @@
+#include "config.h"
+
+#include "gskgldriverprivate.h"
+
+#include "gskdebugprivate.h"
+#include "gskprofilerprivate.h"
+#include "gdk/gdktextureprivate.h"
+
+#include <gdk/gdk.h>
+#include <epoxy/gl.h>
+
+typedef struct {
+  GLuint texture_id;
+  int width;
+  int height;
+  GLuint min_filter;
+  GLuint mag_filter;
+  GArray *fbos;
+  GdkTexture *user;
+  guint in_use : 1;
+  guint permanent : 1;
+} Texture;
+
+typedef struct {
+  GLuint vao_id;
+  GLuint buffer_id;
+  GLuint position_id;
+  GLuint uv_id;
+  GskQuadVertex *quads;
+  int n_quads;
+  guint in_use : 1;
+  guint permanent : 1;
+} Vao;
+
+typedef struct {
+  GLuint fbo_id;
+  GLuint depth_stencil_id;
+} Fbo;
+
+struct _GskGLDriver
+{
+  GObject parent_instance;
+
+  GdkGLContext *gl_context;
+  GskProfiler *profiler;
+  struct {
+    GQuark created_textures;
+    GQuark reused_textures;
+    GQuark surface_uploads;
+  } counters;
+
+  Fbo default_fbo;
+
+  GHashTable *textures;
+  GHashTable *vaos;
+
+  const Texture *bound_source_texture;
+  const Texture *bound_mask_texture;
+  const Vao *bound_vao;
+  const Fbo *bound_fbo;
+
+  int max_texture_size;
+
+  gboolean in_frame : 1;
+};
+
+enum
+{
+  PROP_GL_CONTEXT = 1,
+
+  N_PROPS
+};
+
+static GParamSpec *gsk_gl_driver_properties[N_PROPS];
+
+G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT)
+
+static Texture *
+texture_new (void)
+{
+  return g_slice_new0 (Texture);
+}
+
+static void
+texture_free (gpointer data)
+{
+  Texture *t = data;
+
+  if (t->user)
+    gdk_texture_clear_render_data (t->user);
+
+  g_clear_pointer (&t->fbos, g_array_unref);
+  glDeleteTextures (1, &t->texture_id);
+  g_slice_free (Texture, t);
+}
+
+static void
+fbo_clear (gpointer data)
+{
+  Fbo *f = data;
+
+  if (f->depth_stencil_id != 0)
+    glDeleteRenderbuffers (1, &f->depth_stencil_id);
+
+  glDeleteFramebuffers (1, &f->fbo_id);
+}
+
+static Vao *
+vao_new (void)
+{
+  return g_slice_new0 (Vao);
+}
+
+static void
+vao_free (gpointer data)
+{
+  Vao *v = data;
+
+  g_free (v->quads);
+  glDeleteBuffers (1, &v->buffer_id);
+  glDeleteVertexArrays (1, &v->vao_id);
+  g_slice_free (Vao, v);
+}
+
+static void
+gsk_gl_driver_finalize (GObject *gobject)
+{
+  GskGLDriver *self = GSK_GL_DRIVER (gobject);
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  g_clear_pointer (&self->textures, g_hash_table_unref);
+  g_clear_pointer (&self->vaos, g_hash_table_unref);
+  g_clear_object (&self->profiler);
+
+  if (self->gl_context == gdk_gl_context_get_current ())
+    gdk_gl_context_clear_current ();
+
+  g_clear_object (&self->gl_context);
+
+  G_OBJECT_CLASS (gsk_gl_driver_parent_class)->finalize (gobject);
+}
+
+static void
+gsk_gl_driver_set_property (GObject      *gobject,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GskGLDriver *self = GSK_GL_DRIVER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_GL_CONTEXT:
+      self->gl_context = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_driver_get_property (GObject    *gobject,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GskGLDriver *self = GSK_GL_DRIVER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_GL_CONTEXT:
+      g_value_set_object (value, self->gl_context);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_driver_class_init (GskGLDriverClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property = gsk_gl_driver_set_property;
+  gobject_class->get_property = gsk_gl_driver_get_property;
+  gobject_class->finalize = gsk_gl_driver_finalize;
+
+  gsk_gl_driver_properties[PROP_GL_CONTEXT] =
+    g_param_spec_object ("gl-context", "GL Context", "The GL context used by the driver",
+                         GDK_TYPE_GL_CONTEXT,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, gsk_gl_driver_properties);
+}
+
+static void
+gsk_gl_driver_init (GskGLDriver *self)
+{
+  self->textures = g_hash_table_new_full (NULL, NULL, NULL, texture_free);
+  self->vaos = g_hash_table_new_full (NULL, NULL, NULL, vao_free);
+
+  self->max_texture_size = -1;
+
+#ifdef G_ENABLE_DEBUG
+  self->profiler = gsk_profiler_new ();
+  self->counters.created_textures = gsk_profiler_add_counter (self->profiler,
+                                                              "created_textures",
+                                                              "Textures created this frame",
+                                                              TRUE);
+  self->counters.reused_textures = gsk_profiler_add_counter (self->profiler,
+                                                             "reused_textures",
+                                                             "Textures reused this frame",
+                                                             TRUE);
+  self->counters.surface_uploads = gsk_profiler_add_counter (self->profiler,
+                                                             "surface_uploads",
+                                                             "Texture uploads from surfaces this frame",
+                                                             TRUE);
+#endif
+}
+
+GskGLDriver *
+gsk_gl_driver_new (GdkGLContext *context)
+{
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  return g_object_new (GSK_TYPE_GL_DRIVER,
+                       "gl-context", context,
+                       NULL);
+}
+
+void
+gsk_gl_driver_begin_frame (GskGLDriver *self)
+{
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+  g_return_if_fail (!self->in_frame);
+
+  self->in_frame = TRUE;
+
+  if (self->max_texture_size < 0)
+    {
+      glGetIntegerv (GL_MAX_TEXTURE_SIZE, (GLint *) &self->max_texture_size);
+      GSK_NOTE (OPENGL, g_print ("GL max texture size: %d\n", self->max_texture_size));
+    }
+
+  glBindFramebuffer (GL_FRAMEBUFFER, 0);
+  self->bound_fbo = &self->default_fbo;
+
+  glActiveTexture (GL_TEXTURE0);
+  glBindTexture (GL_TEXTURE_2D, 0);
+
+  glActiveTexture (GL_TEXTURE0 + 1);
+  glBindTexture (GL_TEXTURE_2D, 0);
+
+  glBindVertexArray (0);
+  glUseProgram (0);
+
+  glActiveTexture (GL_TEXTURE0);
+
+  gsk_profiler_reset (self->profiler);
+}
+
+void
+gsk_gl_driver_end_frame (GskGLDriver *self)
+{
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+  g_return_if_fail (self->in_frame);
+
+  self->bound_source_texture = NULL;
+  self->bound_mask_texture = NULL;
+  self->bound_vao = NULL;
+  self->bound_fbo = NULL;
+
+  self->default_fbo.fbo_id = 0;
+
+  GSK_NOTE (OPENGL,
+            g_print ("Textures created: %ld\n"
+                     " Textures reused: %ld\n"
+                     " Surface uploads: %ld\n",
+                     gsk_profiler_counter_get (self->profiler, self->counters.created_textures),
+                     gsk_profiler_counter_get (self->profiler, self->counters.reused_textures),
+                     gsk_profiler_counter_get (self->profiler, self->counters.surface_uploads)));
+  GSK_NOTE (OPENGL,
+            g_print ("*** Frame end: textures=%d, vaos=%d\n",
+                     g_hash_table_size (self->textures),
+                     g_hash_table_size (self->vaos)));
+
+  self->in_frame = FALSE;
+}
+
+int
+gsk_gl_driver_collect_textures (GskGLDriver *driver)
+{
+  GHashTableIter iter;
+  gpointer value_p = NULL;
+  int old_size;
+  /*return;*/
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), 0);
+  g_return_val_if_fail (!driver->in_frame, 0);
+
+  old_size = g_hash_table_size (driver->textures);
+
+  g_hash_table_iter_init (&iter, driver->textures);
+  while (g_hash_table_iter_next (&iter, NULL, &value_p))
+    {
+      Texture *t = value_p;
+
+      if (t->user || t->permanent)
+        continue;
+
+      if (t->in_use)
+        {
+          t->in_use = FALSE;
+          g_clear_pointer (&t->fbos, g_array_unref);
+        }
+      else
+        g_hash_table_iter_remove (&iter);
+    }
+
+  return old_size - g_hash_table_size (driver->textures);
+}
+
+int
+gsk_gl_driver_collect_vaos (GskGLDriver *driver)
+{
+  GHashTableIter iter;
+  gpointer value_p = NULL;
+  int old_size;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), 0);
+  g_return_val_if_fail (!driver->in_frame, 0);
+
+  old_size = g_hash_table_size (driver->vaos);
+
+  g_hash_table_iter_init (&iter, driver->vaos);
+  while (g_hash_table_iter_next (&iter, NULL, &value_p))
+    {
+      Vao *v = value_p;
+
+      if (v->in_use)
+        v->in_use = FALSE;
+      else
+        g_hash_table_iter_remove (&iter);
+    }
+
+  return old_size - g_hash_table_size (driver->vaos);
+}
+
+int
+gsk_gl_driver_get_max_texture_size (GskGLDriver *driver)
+{
+  if (driver->max_texture_size < 0)
+    {
+      if (gdk_gl_context_get_use_es (driver->gl_context))
+        return 2048;
+
+      return 1024;
+    }
+
+  return driver->max_texture_size;
+}
+
+static Texture *
+gsk_gl_driver_get_texture (GskGLDriver *driver,
+                           int          texture_id)
+{
+  Texture *t;
+
+  if (g_hash_table_lookup_extended (driver->textures, GINT_TO_POINTER (texture_id), NULL, (gpointer *) &t))
+    return t;
+
+  return NULL;
+}
+
+static Vao *
+gsk_gl_driver_get_vao (GskGLDriver *driver,
+                       int          vao_id)
+{
+  Vao *v;
+
+  if (g_hash_table_lookup_extended (driver->vaos, GINT_TO_POINTER (vao_id), NULL, (gpointer *) &v))
+    return v;
+
+  return NULL;
+}
+
+static Fbo *
+gsk_gl_driver_get_fbo (GskGLDriver *driver,
+                       int          texture_id)
+{
+  Texture *t = gsk_gl_driver_get_texture (driver, texture_id);
+
+  if (t->fbos == NULL)
+    return &driver->default_fbo;
+
+  return &g_array_index (t->fbos, Fbo, 0);
+}
+
+static Texture *
+find_texture_by_size (GHashTable *textures,
+                      int         width,
+                      int         height)
+{
+  GHashTableIter iter;
+  gpointer value_p = NULL;
+
+  g_hash_table_iter_init (&iter, textures);
+  while (g_hash_table_iter_next (&iter, NULL, &value_p))
+    {
+      Texture *t = value_p;
+
+      if (t->width == width && t->height == height)
+        return t;
+    }
+
+  return NULL;
+}
+
+static Texture *
+create_texture (GskGLDriver *self,
+                float        fwidth,
+                float        fheight)
+{
+  guint texture_id;
+  Texture *t;
+  int width = ceilf (fwidth);
+  int height = ceilf (fheight);
+
+  if (width >= self->max_texture_size ||
+      height >= self->max_texture_size)
+    {
+      g_critical ("Texture %d x %d is bigger than supported texture limit of %d; clipping...",
+                  width, height,
+                  self->max_texture_size);
+
+      width = MIN (width, self->max_texture_size);
+      height = MIN (height, self->max_texture_size);
+    }
+
+  t = find_texture_by_size (self->textures, width, height);
+  if (t != NULL && !t->in_use && t->user == NULL)
+    {
+      GSK_NOTE (OPENGL, g_print ("Reusing Texture(%d) for size %dx%d\n",
+                                 t->texture_id, t->width, t->height));
+      t->in_use = TRUE;
+      gsk_profiler_counter_inc (self->profiler, self->counters.reused_textures);
+      return t;
+    }
+
+  glGenTextures (1, &texture_id);
+
+  t = texture_new ();
+  t->texture_id = texture_id;
+  t->width = width;
+  t->height = height;
+  t->min_filter = GL_NEAREST;
+  t->mag_filter = GL_NEAREST;
+  t->in_use = TRUE;
+  g_hash_table_insert (self->textures, GINT_TO_POINTER (texture_id), t);
+  gsk_profiler_counter_inc (self->profiler, self->counters.created_textures);
+
+  return t;
+}
+
+static void
+gsk_gl_driver_release_texture (gpointer data)
+{
+  Texture *t = data;
+
+  t->user = NULL;
+}
+
+int
+gsk_gl_driver_get_texture_for_texture (GskGLDriver *driver,
+                                       GdkTexture  *texture,
+                                       int          min_filter,
+                                       int          mag_filter)
+{
+  Texture *t;
+  cairo_surface_t *surface;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
+  g_return_val_if_fail (GDK_IS_TEXTURE (texture), -1);
+
+  t = gdk_texture_get_render_data (texture, driver);
+
+  if (t)
+    {
+      if (t->min_filter == min_filter && t->mag_filter == mag_filter)
+        return t->texture_id;
+    }
+
+  t = create_texture (driver, gdk_texture_get_width (texture), gdk_texture_get_height (texture));
+
+  if (gdk_texture_set_render_data (texture, driver, t, gsk_gl_driver_release_texture))
+    t->user = texture;
+
+  surface = gdk_texture_download_surface (texture);
+  gsk_gl_driver_bind_source_texture (driver, t->texture_id);
+  gsk_gl_driver_init_texture_with_surface (driver,
+                                           t->texture_id,
+                                           surface,
+                                           min_filter,
+                                           mag_filter);
+  cairo_surface_destroy (surface);
+
+  return t->texture_id;
+}
+
+int
+gsk_gl_driver_create_permanent_texture (GskGLDriver *self,
+                                        float        width,
+                                        float        height)
+{
+  Texture *t;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), -1);
+
+  t = create_texture (self, width, height);
+  t->permanent = TRUE;
+
+  return t->texture_id;
+}
+
+int
+gsk_gl_driver_create_texture (GskGLDriver *driver,
+                              float        width,
+                              float        height)
+{
+  Texture *t;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
+
+  t = create_texture (driver, width, height);
+
+  return t->texture_id;
+}
+
+static Vao *
+find_vao (GHashTable    *vaos,
+          int            position_id,
+          int            uv_id,
+          int            n_quads,
+          GskQuadVertex *quads)
+{
+  GHashTableIter iter;
+  gpointer value_p = NULL;
+
+  g_hash_table_iter_init (&iter, vaos);
+  while (g_hash_table_iter_next (&iter, NULL, &value_p))
+    {
+      Vao *v = value_p;
+
+      if (v->position_id != position_id || v->uv_id != uv_id)
+        continue;
+
+      if (v->n_quads != n_quads)
+        continue;
+
+      if (memcmp (v->quads, quads, sizeof (GskQuadVertex) * n_quads) == 0)
+        return v;
+    }
+
+  return NULL;
+}
+
+void
+gsk_gl_driver_create_permanent_vao_for_quad (GskGLDriver        *driver,
+                                             int                  n_vertices,
+                                             const GskQuadVertex *quads,
+                                             int                 *out_vao_id,
+                                             int                 *out_vao_buffer_id)
+{
+  GLuint vao_id, buffer_id;
+
+  glGenVertexArrays (1, &vao_id);
+  glBindVertexArray (vao_id);
+
+  glGenBuffers (1, &buffer_id);
+  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
+  glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_vertices, quads, GL_STATIC_DRAW);
+
+  glBindBuffer (GL_ARRAY_BUFFER, 0);
+  glBindVertexArray (0);
+
+  *out_vao_id = buffer_id;
+  *out_vao_buffer_id = vao_id;
+}
+
+int
+gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
+                                   int            position_id,
+                                   int            uv_id,
+                                   int            n_vertices,
+                                   GskQuadVertex *quads)
+
+{
+  GLuint vao_id, buffer_id;
+  Vao *v;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
+  g_return_val_if_fail (driver->in_frame, -1);
+
+  v = find_vao (driver->vaos, position_id, uv_id, n_vertices, quads);
+  if (v != NULL && !v->in_use)
+    {
+      GSK_NOTE (OPENGL, g_print ("Reusing VAO(%d)\n", v->vao_id));
+      v->in_use = TRUE;
+      return v->vao_id;
+    }
+
+  glGenVertexArrays (1, &vao_id);
+  glBindVertexArray (vao_id);
+
+  glGenBuffers (1, &buffer_id);
+  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
+  glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_vertices, quads, GL_STATIC_DRAW);
+
+  if (position_id != -1)
+    {
+      glEnableVertexAttribArray (position_id);
+      glVertexAttribPointer (position_id, 2, GL_FLOAT, GL_FALSE,
+                             sizeof (GskQuadVertex),
+                             (void *) G_STRUCT_OFFSET (GskQuadVertex, position));
+    }
+
+  if (uv_id != -1)
+    {
+      glEnableVertexAttribArray (uv_id);
+      glVertexAttribPointer (uv_id, 2, GL_FLOAT, GL_FALSE,
+                             sizeof (GskQuadVertex),
+                             (void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
+    }
+
+  glBindBuffer (GL_ARRAY_BUFFER, 0);
+  glBindVertexArray (0);
+
+  v = vao_new ();
+  v->vao_id = vao_id;
+  v->buffer_id =  buffer_id;
+  v->position_id = position_id;
+  v->uv_id = uv_id;
+  v->n_quads = n_vertices;
+  v->quads = g_memdup (quads, sizeof (GskQuadVertex) * n_vertices);
+  v->in_use = TRUE;
+  g_hash_table_insert (driver->vaos, GINT_TO_POINTER (vao_id), v);
+
+#ifdef G_ENABLE_DEBUG
+  if (GSK_DEBUG_CHECK (OPENGL))
+    {
+      int i;
+      g_print ("New VAO(%d) for quad[%d] : {\n", v->vao_id, n_vertices);
+      for (i = 0; i < n_vertices; i++)
+        {
+          g_print ("  { x:%.2f, y:%.2f } { u:%.2f, v:%.2f }\n",
+                  quads[i].position[0], quads[i].position[1],
+                  quads[i].uv[0], quads[i].uv[1]);
+        }
+      g_print ("}\n");
+    }
+#endif
+
+  return vao_id;
+}
+
+int
+gsk_gl_driver_create_render_target (GskGLDriver *driver,
+                                    int          texture_id,
+                                    gboolean     add_depth_buffer,
+                                    gboolean     add_stencil_buffer)
+{
+  GLuint fbo_id, depth_stencil_buffer_id;
+  Texture *t;
+  Fbo f;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
+  g_return_val_if_fail (driver->in_frame, -1);
+
+  t = gsk_gl_driver_get_texture (driver, texture_id);
+  if (t == NULL)
+    return -1;
+
+  if (t->fbos == NULL)
+    {
+      t->fbos = g_array_new (FALSE, FALSE, sizeof (Fbo));
+      g_array_set_clear_func (t->fbos, fbo_clear);
+    }
+
+  glGenFramebuffers (1, &fbo_id);
+  glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
+  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, t->texture_id, 0);
+
+  if (add_depth_buffer || add_stencil_buffer)
+    glGenRenderbuffersEXT (1, &depth_stencil_buffer_id);
+  else
+    depth_stencil_buffer_id = 0;
+
+  glBindRenderbuffer (GL_RENDERBUFFER, depth_stencil_buffer_id);
+
+  if (add_depth_buffer || add_stencil_buffer)
+    {
+      if (add_stencil_buffer)
+        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, t->width, t->height);
+      else
+        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, t->width, t->height);
+
+      if (add_depth_buffer)
+        glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                                   GL_RENDERBUFFER, depth_stencil_buffer_id);
+
+      if (add_stencil_buffer)
+        glFramebufferRenderbufferEXT (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+                                      GL_RENDERBUFFER, depth_stencil_buffer_id);
+    }
+
+  f.fbo_id = fbo_id;
+  f.depth_stencil_id = depth_stencil_buffer_id;
+
+  g_array_append_val (t->fbos, f);
+
+  g_assert_cmpint (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);
+  glBindFramebuffer (GL_FRAMEBUFFER, driver->default_fbo.fbo_id);
+
+  return fbo_id;
+}
+
+void
+gsk_gl_driver_bind_source_texture (GskGLDriver *driver,
+                                   int          texture_id)
+{
+  Texture *t;
+
+  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
+  g_return_if_fail (driver->in_frame);
+
+  t = gsk_gl_driver_get_texture (driver, texture_id);
+  if (t == NULL)
+    {
+      g_critical ("No texture %d found.", texture_id);
+      return;
+    }
+
+  if (driver->bound_source_texture != t)
+    {
+      glActiveTexture (GL_TEXTURE0);
+      glBindTexture (GL_TEXTURE_2D, t->texture_id);
+
+      driver->bound_source_texture = t;
+    }
+}
+
+void
+gsk_gl_driver_bind_mask_texture (GskGLDriver *driver,
+                                 int          texture_id)
+{
+  Texture *t;
+
+  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
+  g_return_if_fail (driver->in_frame);
+
+  t = gsk_gl_driver_get_texture (driver, texture_id);
+  if (t == NULL)
+    {
+      g_critical ("No texture %d found.", texture_id);
+      return;
+    }
+
+  if (driver->bound_mask_texture != t)
+    {
+      glActiveTexture (GL_TEXTURE0 + 1);
+      glBindTexture (GL_TEXTURE_2D, t->texture_id);
+
+      glActiveTexture (GL_TEXTURE0);
+
+      driver->bound_mask_texture = t;
+    }
+}
+
+void
+gsk_gl_driver_bind_vao (GskGLDriver *driver,
+                        int          vao_id)
+{
+  Vao *v;
+
+  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
+  g_return_if_fail (driver->in_frame);
+
+  v = gsk_gl_driver_get_vao (driver, vao_id);
+  if (v == NULL)
+    {
+      g_critical ("No VAO %d found.", vao_id);
+      return;
+    }
+
+  if (driver->bound_vao != v)
+    {
+      glBindVertexArray (v->vao_id);
+      glBindBuffer (GL_ARRAY_BUFFER, v->buffer_id);
+
+      if (v->position_id != -1)
+        glEnableVertexAttribArray (v->position_id);
+
+      if (v->uv_id != -1)
+        glEnableVertexAttribArray (v->uv_id);
+
+      driver->bound_vao = v;
+    }
+}
+
+gboolean
+gsk_gl_driver_bind_render_target (GskGLDriver *driver,
+                                  int          texture_id)
+{
+  int status;
+  Fbo *f;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), FALSE);
+  g_return_val_if_fail (driver->in_frame, FALSE);
+
+  if (texture_id == 0)
+    {
+      glBindFramebuffer (GL_FRAMEBUFFER, 0);
+      driver->bound_fbo = &driver->default_fbo;
+      goto out;
+    }
+
+  f = gsk_gl_driver_get_fbo (driver, texture_id);
+  if (f == NULL)
+    {
+      g_critical ("No render target associated to texture %d found.", texture_id);
+      return FALSE;
+    }
+
+  if (f != driver->bound_fbo)
+    {
+      glBindFramebuffer (GL_FRAMEBUFFER, f->fbo_id);
+
+      driver->bound_fbo = f;
+    }
+
+out:
+
+  if (texture_id != 0)
+    {
+      status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
+      g_assert_cmpint (status, ==, GL_FRAMEBUFFER_COMPLETE);
+    }
+
+  return TRUE;
+}
+
+void
+gsk_gl_driver_destroy_texture (GskGLDriver *driver,
+                               int          texture_id)
+{
+  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
+
+  g_hash_table_remove (driver->textures, GINT_TO_POINTER (texture_id));
+}
+
+void
+gsk_gl_driver_destroy_vao (GskGLDriver *driver,
+                           int          vao_id)
+{
+  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
+
+  g_hash_table_remove (driver->vaos, GINT_TO_POINTER (vao_id));
+}
+
+static void
+gsk_gl_driver_set_texture_parameters (GskGLDriver *driver,
+                                      int          min_filter,
+                                      int          mag_filter)
+{
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+void
+gsk_gl_driver_init_texture_empty (GskGLDriver *driver,
+                                  int          texture_id)
+{
+  Texture *t;
+
+  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
+
+  t = gsk_gl_driver_get_texture (driver, texture_id);
+  if (t == NULL)
+    {
+      g_critical ("No texture %d found.", texture_id);
+      return;
+    }
+
+  if (!(driver->bound_source_texture == t || driver->bound_mask_texture == t))
+    {
+      g_critical ("You must bind the texture before initializing it.");
+      return;
+    }
+
+  gsk_gl_driver_set_texture_parameters (driver, t->min_filter, t->mag_filter);
+
+  if (gdk_gl_context_get_use_es (driver->gl_context))
+    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+  else
+    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+  glBindTexture (GL_TEXTURE_2D, 0);
+}
+
+void
+gsk_gl_driver_init_texture_with_surface (GskGLDriver     *self,
+                                         int              texture_id,
+                                         cairo_surface_t *surface,
+                                         int              min_filter,
+                                         int              mag_filter)
+{
+  Texture *t;
+
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+
+  t = gsk_gl_driver_get_texture (self, texture_id);
+  if (t == NULL)
+    {
+      g_critical ("No texture %d found.", texture_id);
+      return;
+    }
+
+  if (!(self->bound_source_texture == t || self->bound_mask_texture == t))
+    {
+      g_critical ("You must bind the texture before initializing it.");
+      return;
+    }
+
+  gsk_gl_driver_set_texture_parameters (self, min_filter, mag_filter);
+
+  gdk_cairo_surface_upload_to_gl (surface, GL_TEXTURE_2D, t->width, t->height, NULL);
+  gsk_profiler_counter_inc (self->profiler, self->counters.surface_uploads);
+
+  t->min_filter = min_filter;
+  t->mag_filter = mag_filter;
+
+  if (t->min_filter != GL_NEAREST)
+    glGenerateMipmap (GL_TEXTURE_2D);
+}
diff --git a/gsk/gl/gskgldriverprivate.h b/gsk/gl/gskgldriverprivate.h
new file mode 100644 (file)
index 0000000..f5f9986
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef __GSK_GL_DRIVER_PRIVATE_H__
+#define __GSK_GL_DRIVER_PRIVATE_H__
+
+#include <cairo.h>
+#include <gdk/gdk.h>
+#include <graphene.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_DRIVER (gsk_gl_driver_get_type ())
+
+G_DECLARE_FINAL_TYPE (GskGLDriver, gsk_gl_driver, GSK, GL_DRIVER, GObject)
+
+typedef struct {
+  float position[2];
+  float uv[2];
+} GskQuadVertex;
+
+GskGLDriver *   gsk_gl_driver_new                       (GdkGLContext    *context);
+
+int             gsk_gl_driver_get_max_texture_size      (GskGLDriver     *driver);
+
+void            gsk_gl_driver_begin_frame               (GskGLDriver     *driver);
+void            gsk_gl_driver_end_frame                 (GskGLDriver     *driver);
+
+int             gsk_gl_driver_get_texture_for_texture   (GskGLDriver     *driver,
+                                                         GdkTexture      *texture,
+                                                         int              min_filter,
+                                                         int              mag_filter);
+int             gsk_gl_driver_create_permanent_texture  (GskGLDriver     *driver,
+                                                         float            width,
+                                                         float            height);
+int             gsk_gl_driver_create_texture            (GskGLDriver     *driver,
+                                                         float            width,
+                                                         float            height);
+int             gsk_gl_driver_create_vao_for_quad       (GskGLDriver     *driver,
+                                                         int              position_id,
+                                                         int              uv_id,
+                                                         int              n_vertices,
+                                                         GskQuadVertex   *vertices);
+void           gsk_gl_driver_create_permanent_vao_for_quad (GskGLDriver        *driver,
+                                                            int                  n_vertices,
+                                                            const GskQuadVertex *quads,
+                                                            int                 *out_vao_id,
+                                                            int                 *out_vao_buffer_id);
+int             gsk_gl_driver_create_render_target      (GskGLDriver     *driver,
+                                                         int              texture_id,
+                                                         gboolean         add_depth_buffer,
+                                                         gboolean         add_stencil_buffer);
+
+void            gsk_gl_driver_bind_source_texture       (GskGLDriver     *driver,
+                                                         int              texture_id);
+void            gsk_gl_driver_bind_mask_texture         (GskGLDriver     *driver,
+                                                         int              texture_id);
+void            gsk_gl_driver_bind_vao                  (GskGLDriver     *driver,
+                                                         int              vao_id);
+gboolean        gsk_gl_driver_bind_render_target        (GskGLDriver     *driver,
+                                                         int              texture_id);
+
+void            gsk_gl_driver_init_texture_empty        (GskGLDriver     *driver,
+                                                         int              texture_id);
+void            gsk_gl_driver_init_texture_with_surface (GskGLDriver     *driver,
+                                                         int              texture_id,
+                                                         cairo_surface_t *surface,
+                                                         int              min_filter,
+                                                         int              mag_filter);
+
+void            gsk_gl_driver_destroy_texture           (GskGLDriver     *driver,
+                                                         int              texture_id);
+void            gsk_gl_driver_destroy_vao               (GskGLDriver     *driver,
+                                                         int              vao_id);
+
+int             gsk_gl_driver_collect_textures          (GskGLDriver     *driver);
+int             gsk_gl_driver_collect_vaos              (GskGLDriver     *driver);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_DRIVER_PRIVATE_H__ */
diff --git a/gsk/gl/gskglglyphcache.c b/gsk/gl/gskglglyphcache.c
new file mode 100644 (file)
index 0000000..9e253e2
--- /dev/null
@@ -0,0 +1,445 @@
+#include "config.h"
+
+#include "gskglglyphcacheprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskdebugprivate.h"
+#include "gskprivate.h"
+
+#include <graphene.h>
+#include <cairo/cairo.h>
+#include <epoxy/gl.h>
+
+/* Parameters for our cache eviction strategy.
+ *
+ * Each cached glyph has an age that gets reset every time a cached glyph gets used.
+ * Glyphs that have not been used for the MAX_AGE frames are considered old. We keep
+ * count of the pixels of each atlas that are taken up by old glyphs. We check the
+ * fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD, then
+ * we drop the atlas an all the glyphs contained in it from the cache.
+ */
+
+#define MAX_AGE 60
+#define CHECK_INTERVAL 10
+#define MAX_OLD 0.333
+
+#define ATLAS_SIZE 512
+
+typedef struct
+{
+  PangoFont *font;
+  PangoGlyph glyph;
+  guint scale; /* times 1024 */
+} GlyphCacheKey;
+
+typedef struct
+{
+  GlyphCacheKey *key;
+  GskGLCachedGlyph *value;
+  cairo_surface_t *surface;
+} DirtyGlyph;
+
+
+static guint    glyph_cache_hash       (gconstpointer v);
+static gboolean glyph_cache_equal      (gconstpointer v1,
+                                        gconstpointer v2);
+static void     glyph_cache_key_free   (gpointer      v);
+static void     glyph_cache_value_free (gpointer      v);
+static void     dirty_glyph_free       (gpointer      v);
+
+static GskGLGlyphAtlas *
+create_atlas (GskGLGlyphCache *cache)
+{
+  GskGLGlyphAtlas *atlas;
+
+  atlas = g_new0 (GskGLGlyphAtlas, 1);
+  atlas->width = ATLAS_SIZE;
+  atlas->height = ATLAS_SIZE;
+  atlas->y0 = 1;
+  atlas->y = 1;
+  atlas->x = 1;
+  atlas->image = NULL;
+  atlas->num_glyphs = 0;
+  atlas->dirty_glyphs = NULL;
+
+  return atlas;
+}
+
+static void
+free_atlas (gpointer v)
+{
+  GskGLGlyphAtlas *atlas = v;
+
+  if (atlas->image)
+    {
+      g_assert (atlas->image->texture_id == 0);
+      g_free (atlas->image);
+    }
+  g_list_free_full (atlas->dirty_glyphs, dirty_glyph_free);
+  g_free (atlas);
+}
+
+void
+gsk_gl_glyph_cache_init (GskGLGlyphCache *self,
+                         GskGLDriver     *gl_driver)
+{
+  self->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
+                                            glyph_cache_key_free, glyph_cache_value_free);
+  self->atlases = g_ptr_array_new_with_free_func (free_atlas);
+  g_ptr_array_add (self->atlases, create_atlas (self));
+
+  self->gl_driver = gl_driver;
+}
+
+void
+gsk_gl_glyph_cache_free (GskGLGlyphCache *self)
+{
+  guint i;
+
+  for (i = 0; i < self->atlases->len; i ++)
+    {
+      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
+
+      if (atlas->image)
+        {
+          gsk_gl_image_destroy (atlas->image, self->gl_driver);
+          atlas->image->texture_id = 0;
+        }
+    }
+
+  g_ptr_array_unref (self->atlases);
+  g_hash_table_unref (self->hash_table);
+}
+
+static gboolean
+glyph_cache_equal (gconstpointer v1, gconstpointer v2)
+{
+  const GlyphCacheKey *key1 = v1;
+  const GlyphCacheKey *key2 = v2;
+
+  return key1->font == key2->font &&
+         key1->glyph == key2->glyph &&
+         key1->scale == key2->scale;
+}
+
+static guint
+glyph_cache_hash (gconstpointer v)
+{
+  const GlyphCacheKey *key = v;
+
+  return GPOINTER_TO_UINT (key->font) ^ key->glyph ^ key->scale;
+}
+
+static void
+glyph_cache_key_free (gpointer v)
+{
+  GlyphCacheKey *f = v;
+
+  g_object_unref (f->font);
+  g_free (f);
+}
+
+static void
+glyph_cache_value_free (gpointer v)
+{
+  g_free (v);
+}
+
+static void
+dirty_glyph_free (gpointer v)
+{
+  DirtyGlyph *glyph = v;
+
+  if (glyph->surface)
+    cairo_surface_destroy (glyph->surface);
+  g_free (glyph);
+}
+
+static void
+add_to_cache (GskGLGlyphCache  *cache,
+              GlyphCacheKey        *key,
+              GskGLCachedGlyph *value)
+{
+  GskGLGlyphAtlas *atlas;
+  int i;
+  DirtyGlyph *dirty;
+  int width = value->draw_width * key->scale / 1024;
+  int height = value->draw_height * key->scale / 1024;
+
+  for (i = 0; i < cache->atlases->len; i++)
+    {
+      int x, y, y0;
+
+      atlas = g_ptr_array_index (cache->atlases, i);
+      x = atlas->x;
+      y = atlas->y;
+      y0 = atlas->y0;
+
+      if (atlas->x + width + 1 >= atlas->width)
+        {
+          /* start a new row */
+          y0 = y + 1;
+          x = 1;
+        }
+
+      if (y0 + height + 1 >= atlas->height)
+        continue;
+
+      atlas->y0 = y0;
+      atlas->x = x;
+      atlas->y = y;
+      break;
+    }
+
+  if (i == cache->atlases->len)
+    {
+      atlas = create_atlas (cache);
+      g_ptr_array_add (cache->atlases, atlas);
+    }
+
+  value->tx = (float)atlas->x / atlas->width;
+  value->ty = (float)atlas->y0 / atlas->height;
+  value->tw = (float)width / atlas->width;
+  value->th = (float)height / atlas->height;
+
+  value->atlas = atlas;
+
+  dirty = g_new0 (DirtyGlyph, 1);
+  dirty->key = key;
+  dirty->value = value;
+  atlas->dirty_glyphs = g_list_prepend (atlas->dirty_glyphs, dirty);
+
+  atlas->x = atlas->x + width + 1;
+  atlas->y = MAX (atlas->y, atlas->y0 + height + 1);
+
+  atlas->num_glyphs++;
+
+#ifdef G_ENABLE_DEBUG
+  if (GSK_DEBUG_CHECK(GLYPH_CACHE))
+    {
+      g_print ("Glyph cache:\n");
+      for (i = 0; i < cache->atlases->len; i++)
+        {
+          atlas = g_ptr_array_index (cache->atlases, i);
+          g_print ("\tGskGLGlyphAtlas %d (%dx%d): %d glyphs (%d dirty), %.2g%% old pixels, filled to %d, %d / %d\n",
+                   i, atlas->width, atlas->height,
+                   atlas->num_glyphs, g_list_length (atlas->dirty_glyphs),
+                   100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height),
+                   atlas->x, atlas->y0, atlas->y);
+        }
+    }
+#endif
+}
+
+static void
+render_glyph (const GskGLGlyphAtlas *atlas,
+              DirtyGlyph            *glyph,
+              GskImageRegion        *region)
+{
+  GlyphCacheKey *key = glyph->key;
+  GskGLCachedGlyph *value = glyph->value;
+  cairo_surface_t *surface;
+  cairo_t *cr;
+  cairo_scaled_font_t *scaled_font;
+  cairo_glyph_t cg;
+
+  scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
+  if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
+    return;
+
+  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                        value->draw_width * key->scale / 1024,
+                                        value->draw_height * key->scale / 1024);
+  cairo_surface_set_device_scale (surface, key->scale / 1024.0, key->scale / 1024.0);
+
+  cr = cairo_create (surface);
+
+  cairo_set_scaled_font (cr, scaled_font);
+  cairo_set_source_rgba (cr, 1, 1, 1, 1);
+
+  cg.index = key->glyph;
+  cg.x = - value->draw_x;
+  cg.y = - value->draw_y;
+
+  cairo_show_glyphs (cr, &cg, 1);
+
+  cairo_destroy (cr);
+
+  glyph->surface = surface;
+
+  region->data = cairo_image_surface_get_data (surface);
+  region->width = cairo_image_surface_get_width (surface);
+  region->height = cairo_image_surface_get_height (surface);
+  region->stride = cairo_image_surface_get_stride (surface);
+  region->x = (gsize)(value->tx * atlas->width);
+  region->y = (gsize)(value->ty * atlas->height);
+}
+
+static void
+upload_dirty_glyphs (GskGLGlyphCache *self,
+                     GskGLGlyphAtlas *atlas)
+{
+  GList *l;
+  guint num_regions;
+  GskImageRegion *regions;
+  int i;
+
+  num_regions = g_list_length (atlas->dirty_glyphs);
+  regions = alloca (sizeof (GskImageRegion) * num_regions);
+
+  for (l = atlas->dirty_glyphs, i = 0; l; l = l->next, i++)
+    render_glyph (atlas, (DirtyGlyph *)l->data, &regions[i]);
+
+  GSK_NOTE (GLYPH_CACHE,
+            g_print ("uploading %d glyphs to cache\n", num_regions));
+
+
+  gsk_gl_image_upload_regions (atlas->image, self->gl_driver, num_regions, regions);
+
+  g_list_free_full (atlas->dirty_glyphs, dirty_glyph_free);
+  atlas->dirty_glyphs = NULL;
+}
+
+const GskGLCachedGlyph *
+gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
+                           gboolean         create,
+                           PangoFont       *font,
+                           PangoGlyph       glyph,
+                           float            scale)
+{
+  GlyphCacheKey lookup_key;
+  GskGLCachedGlyph *value;
+
+  lookup_key.font = font;
+  lookup_key.glyph = glyph;
+  lookup_key.scale = (guint)(scale * 1024);
+
+  value = g_hash_table_lookup (cache->hash_table, &lookup_key);
+
+  if (value)
+    {
+      if (cache->timestamp - value->timestamp >= MAX_AGE)
+        {
+          GskGLGlyphAtlas *atlas = value->atlas;
+
+          if (atlas)
+            atlas->old_pixels -= value->draw_width * value->draw_height;
+
+          value->timestamp = cache->timestamp;
+        }
+    }
+
+  if (create && value == NULL)
+    {
+      GlyphCacheKey *key;
+      PangoRectangle ink_rect;
+
+      key = g_new0 (GlyphCacheKey, 1);
+      value = g_new0 (GskGLCachedGlyph, 1);
+
+      pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
+      pango_extents_to_pixels (&ink_rect, NULL);
+
+      value->draw_x = ink_rect.x;
+      value->draw_y = ink_rect.y;
+      value->draw_width = ink_rect.width;
+      value->draw_height = ink_rect.height;
+      value->timestamp = cache->timestamp;
+      value->atlas = NULL; /* For now */
+
+      key->font = g_object_ref (font);
+      key->glyph = glyph;
+      key->scale = (guint)(scale * 1024);
+
+      if (ink_rect.width > 0 && ink_rect.height > 0)
+        add_to_cache (cache, key, value);
+
+      g_hash_table_insert (cache->hash_table, key, value);
+    }
+
+  return value;
+}
+
+GskGLImage *
+gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache *self,
+                                    const GskGLCachedGlyph *glyph)
+{
+  GskGLGlyphAtlas *atlas = glyph->atlas;
+
+  g_assert (atlas != NULL);
+
+
+  if (atlas->image == NULL)
+    {
+      atlas->image = g_new0 (GskGLImage, 1);
+      gsk_gl_image_create (atlas->image, self->gl_driver, atlas->width, atlas->height);
+    }
+
+  if (atlas->dirty_glyphs)
+    upload_dirty_glyphs (self, atlas);
+
+  return atlas->image;
+}
+
+void
+gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
+{
+  int i;
+  GHashTableIter iter;
+  GlyphCacheKey *key;
+  GskGLCachedGlyph *value;
+  guint dropped = 0;
+
+  self->timestamp++;
+
+
+  if (self->timestamp % CHECK_INTERVAL != 0)
+    return;
+
+  /* look for glyphs that have grown old since last time */
+  g_hash_table_iter_init (&iter, self->hash_table);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+    {
+      guint age;
+
+      age = self->timestamp - value->timestamp;
+      if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
+        {
+          GskGLGlyphAtlas *atlas = value->atlas;
+
+          if (atlas)
+            atlas->old_pixels += value->draw_width * value->draw_height;
+        }
+    }
+
+  /* look for atlases to drop, and create a mapping of updated texture indices */
+  for (i = self->atlases->len - 1; i >= 0; i--)
+    {
+      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
+
+      if (atlas->old_pixels > MAX_OLD * atlas->width * atlas->height)
+        {
+          GSK_NOTE(GLYPH_CACHE,
+                   g_print ("Dropping atlas %d (%g.2%% old)\n",
+                            i, 100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height)));
+
+          if (atlas->image)
+            {
+              gsk_gl_image_destroy (atlas->image, self->gl_driver);
+              atlas->image->texture_id = 0;
+            }
+
+          /* Remove all glyphs that point to this atlas */
+          g_hash_table_iter_init (&iter, self->hash_table);
+          while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+            {
+              if (value->atlas == atlas)
+                g_hash_table_iter_remove (&iter);
+            }
+          /* TODO: The above loop inside this other loop could be slow... */
+
+          g_ptr_array_remove_index (self->atlases, i);
+        }
+    }
+
+  GSK_NOTE(GLYPH_CACHE, g_print ("Dropped %d glyphs\n", dropped));
+}
diff --git a/gsk/gl/gskglglyphcacheprivate.h b/gsk/gl/gskglglyphcacheprivate.h
new file mode 100644 (file)
index 0000000..7166402
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef __GSK_GL_GLYPH_CACHE_PRIVATE_H__
+#define __GSK_GL_GLYPH_CACHE_PRIVATE_H__
+
+#include "gskgldriverprivate.h"
+#include "gskglimageprivate.h"
+#include <pango/pango.h>
+#include <gdk/gdk.h>
+
+typedef struct
+{
+  GskGLDriver *gl_driver;
+
+  GHashTable *hash_table;
+  GPtrArray *atlases;
+
+  guint64 timestamp;
+} GskGLGlyphCache;
+
+
+typedef struct
+{
+  GskGLImage *image;
+  int width, height;
+  int x, y, y0;
+  int num_glyphs;
+  GList *dirty_glyphs;
+  guint old_pixels;
+} GskGLGlyphAtlas;
+
+typedef struct
+{
+  GskGLGlyphAtlas *atlas;
+
+  float tx;
+  float ty;
+  float tw;
+  float th;
+
+  int draw_x;
+  int draw_y;
+  int draw_width;
+  int draw_height;
+
+  guint64 timestamp;
+} GskGLCachedGlyph;
+
+void                     gsk_gl_glyph_cache_init            (GskGLGlyphCache        *self,
+                                                             GskGLDriver            *gl_driver);
+void                     gsk_gl_glyph_cache_free            (GskGLGlyphCache        *self);
+void                     gsk_gl_glyph_cache_begin_frame     (GskGLGlyphCache        *self);
+GskGLImage *             gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache        *self,
+                                                             const GskGLCachedGlyph *glyph);
+const GskGLCachedGlyph * gsk_gl_glyph_cache_lookup          (GskGLGlyphCache        *self,
+                                                             gboolean                create,
+                                                             PangoFont              *font,
+                                                             PangoGlyph              glyph,
+                                                             float                   scale);
+
+#endif
diff --git a/gsk/gl/gskglimage.c b/gsk/gl/gskglimage.c
new file mode 100644 (file)
index 0000000..6d91b35
--- /dev/null
@@ -0,0 +1,68 @@
+
+#include "gskglimageprivate.h"
+#include <epoxy/gl.h>
+
+void
+gsk_gl_image_create (GskGLImage  *self,
+                     GskGLDriver *gl_driver,
+                     int          width,
+                     int          height)
+{
+  self->texture_id = gsk_gl_driver_create_permanent_texture (gl_driver, width, height);
+  self->width = width;
+  self->height = height;
+
+  gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);
+  gsk_gl_driver_init_texture_empty (gl_driver, self->texture_id);
+}
+
+void
+gsk_gl_image_destroy (GskGLImage  *self,
+                      GskGLDriver *gl_driver)
+{
+  gsk_gl_driver_destroy_texture (gl_driver, self->texture_id);
+}
+
+void
+gsk_gl_image_write_to_png (const GskGLImage *self,
+                           GskGLDriver      *gl_driver,
+                           const char       *filename)
+{
+  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, self->width);
+  guchar *data = g_malloc (self->height * stride);
+  cairo_surface_t *s;
+
+  gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);
+  glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+  s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, self->width, self->height, stride);
+  cairo_surface_write_to_png (s, filename);
+
+  cairo_surface_destroy (s);
+  g_free (data);
+}
+
+void
+gsk_gl_image_upload_regions (GskGLImage           *self,
+                             GskGLDriver          *gl_driver,
+                             guint                 n_regions,
+                             const GskImageRegion *regions)
+{
+  guint i;
+
+  for (i = 0; i < n_regions; i ++)
+    {
+      const GskImageRegion *region = &regions[i];
+
+      gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);
+      glBindTexture (GL_TEXTURE_2D, self->texture_id);
+
+      glTexSubImage2D (GL_TEXTURE_2D, 0, region->x, region->y, region->width, region->height,
+                       GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, region->data);
+    }
+
+#ifdef G_ENABLE_DEBUG
+  /*gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);*/
+  /*gsk_gl_image_dump (self, gl_driver, "/home/baedert/atlases/test_dump.png");*/
+#endif
+}
+
diff --git a/gsk/gl/gskglimageprivate.h b/gsk/gl/gskglimageprivate.h
new file mode 100644 (file)
index 0000000..5a58fea
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef __GSK_GL_IMAGE_H__
+#define __GSK_GL_IMAGE_H__
+
+#include "gskgldriverprivate.h"
+#include <cairo/cairo.h>
+
+typedef struct
+{
+  guint texture_id;
+  int width;
+  int height;
+} GskGLImage;
+
+typedef struct
+{
+  guchar *data;
+  gsize width;
+  gsize height;
+  gsize stride;
+  gsize x;
+  gsize y;
+} GskImageRegion;
+
+void gsk_gl_image_create         (GskGLImage           *self,
+                                  GskGLDriver          *gl_driver,
+                                  int                   width,
+                                  int                   height);
+void gsk_gl_image_destroy        (GskGLImage           *self,
+                                  GskGLDriver          *gl_driver);
+void gsk_gl_image_write_to_png   (const GskGLImage     *self,
+                                  GskGLDriver          *gl_driver,
+                                  const char           *filename);
+void gsk_gl_image_upload_regions (GskGLImage           *self,
+                                  GskGLDriver          *gl_driver,
+                                  guint                 n_regions,
+                                  const GskImageRegion *regions);
+
+
+#endif
+
diff --git a/gsk/gl/gskglprofiler.c b/gsk/gl/gskglprofiler.c
new file mode 100644 (file)
index 0000000..0d58ee8
--- /dev/null
@@ -0,0 +1,175 @@
+#include "config.h"
+
+#include "gskglprofilerprivate.h"
+
+#include <epoxy/gl.h>
+
+#define N_QUERIES       4
+
+struct _GskGLProfiler
+{
+  GObject parent_instance;
+
+  GdkGLContext *gl_context;
+
+  /* Creating GL queries is kind of expensive, so we pay the
+   * price upfront and create a circular buffer of queries
+   */
+  GLuint gl_queries[N_QUERIES];
+  GLuint active_query;
+
+  gboolean has_timer : 1;
+  gboolean first_frame : 1;
+};
+
+enum {
+  PROP_GL_CONTEXT = 1,
+
+  N_PROPERTIES
+};
+
+static GParamSpec *gsk_gl_profiler_properties[N_PROPERTIES];
+
+G_DEFINE_TYPE (GskGLProfiler, gsk_gl_profiler, G_TYPE_OBJECT)
+
+static void
+gsk_gl_profiler_finalize (GObject *gobject)
+{
+  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
+
+  glDeleteQueries (N_QUERIES, self->gl_queries);
+
+  g_clear_object (&self->gl_context);
+
+  G_OBJECT_CLASS (gsk_gl_profiler_parent_class)->finalize (gobject);
+}
+
+static void
+gsk_gl_profiler_set_property (GObject      *gobject,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_GL_CONTEXT:
+      self->gl_context = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_profiler_get_property (GObject    *gobject,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_GL_CONTEXT:
+      g_value_set_object (value, self->gl_context);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_profiler_class_init (GskGLProfilerClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property = gsk_gl_profiler_set_property;
+  gobject_class->get_property = gsk_gl_profiler_get_property;
+  gobject_class->finalize = gsk_gl_profiler_finalize;
+
+  gsk_gl_profiler_properties[PROP_GL_CONTEXT] =
+    g_param_spec_object ("gl-context",
+                         "GL Context",
+                         "The GdkGLContext used by the GL profiler",
+                         GDK_TYPE_GL_CONTEXT,
+                         G_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPERTIES, gsk_gl_profiler_properties);
+}
+
+static void
+gsk_gl_profiler_init (GskGLProfiler *self)
+{
+  glGenQueries (N_QUERIES, self->gl_queries);
+
+  self->first_frame = TRUE;
+  self->has_timer = epoxy_has_gl_extension ("GL_ARB_timer_query");
+}
+
+GskGLProfiler *
+gsk_gl_profiler_new (GdkGLContext *context)
+{
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  return g_object_new (GSK_TYPE_GL_PROFILER, "gl-context", context, NULL);
+}
+
+void
+gsk_gl_profiler_begin_gpu_region (GskGLProfiler *profiler)
+{
+  GLuint query_id;
+
+  g_return_if_fail (GSK_IS_GL_PROFILER (profiler));
+
+  if (!profiler->has_timer)
+    return;
+
+  query_id = profiler->gl_queries[profiler->active_query];
+  glBeginQuery (GL_TIME_ELAPSED, query_id);
+}
+
+guint64
+gsk_gl_profiler_end_gpu_region (GskGLProfiler *profiler)
+{
+  GLuint last_query_id;
+  GLint res;
+  GLuint64 elapsed;
+
+  g_return_val_if_fail (GSK_IS_GL_PROFILER (profiler), 0);
+
+  if (!profiler->has_timer)
+    return 0;
+
+  glEndQuery (GL_TIME_ELAPSED);
+
+  if (profiler->active_query == 0)
+    last_query_id = N_QUERIES - 1;
+  else
+    last_query_id = profiler->active_query - 1;
+
+  /* Advance iterator */
+  profiler->active_query += 1;
+  if (profiler->active_query == N_QUERIES)
+    profiler->active_query = 0;
+
+  /* If this is the first frame we already have a result */
+  if (profiler->first_frame)
+    {
+      profiler->first_frame = FALSE;
+      return 0;
+    }
+
+  glGetQueryObjectiv (profiler->gl_queries[last_query_id], GL_QUERY_RESULT_AVAILABLE, &res);
+  if (res == 1)
+    glGetQueryObjectui64v (profiler->gl_queries[last_query_id], GL_QUERY_RESULT, &elapsed);
+  else
+    elapsed = 0;
+
+  return elapsed;
+}
diff --git a/gsk/gl/gskglprofilerprivate.h b/gsk/gl/gskglprofilerprivate.h
new file mode 100644 (file)
index 0000000..5b2a24b
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef __GSK_GL_PROFILER_PRIVATE_H__
+#define __GSK_GL_PROFILER_PRIVATE_H__
+
+#include <gsk/gsktypes.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_PROFILER (gsk_gl_profiler_get_type ())
+G_DECLARE_FINAL_TYPE (GskGLProfiler, gsk_gl_profiler, GSK, GL_PROFILER, GObject)
+
+GskGLProfiler * gsk_gl_profiler_new                     (GdkGLContext  *context);
+
+void            gsk_gl_profiler_begin_gpu_region        (GskGLProfiler *profiler);
+guint64         gsk_gl_profiler_end_gpu_region          (GskGLProfiler *profiler);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_PROFILER_PRIVATE_H__ */
diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c
new file mode 100644 (file)
index 0000000..3398861
--- /dev/null
@@ -0,0 +1,1706 @@
+#include "config.h"
+
+#include "gskglrendererprivate.h"
+
+#include "gskdebugprivate.h"
+#include "gskenums.h"
+#include "gskgldriverprivate.h"
+#include "gskglprofilerprivate.h"
+#include "gskprofilerprivate.h"
+#include "gskrendererprivate.h"
+#include "gskrendernodeprivate.h"
+#include "gskshaderbuilderprivate.h"
+#include "gskglglyphcacheprivate.h"
+#include "gdk/gdktextureprivate.h"
+
+#include "gskprivate.h"
+
+#include <epoxy/gl.h>
+#include <cairo-ft.h>
+
+#define SHADER_VERSION_GLES             100
+#define SHADER_VERSION_GL2_LEGACY       110
+#define SHADER_VERSION_GL3_LEGACY       130
+#define SHADER_VERSION_GL3              150
+
+#define ORTHO_NEAR_PLANE        -10000
+#define ORTHO_FAR_PLANE          10000
+
+#define N_VERTICES      6
+#define N_PROGRAMS      6
+
+#define HIGHLIGHT_FALLBACK 0
+#define DEBUG_OPS          0
+
+#if DEBUG_OPS
+#define OP_PRINT(format, ...) g_print(format, ## __VA_ARGS__)
+#else
+#define OP_PRINT(format, ...)
+#endif
+
+#define INIT_PROGRAM_UNIFORM_LOCATION(program_name, location_name, uniform_name) \
+              G_STMT_START{\
+                self->program_name.location_name = glGetUniformLocation(self->program_name.id, uniform_name);\
+                g_assert (self->program_name.location_name != 0); \
+              }G_STMT_END
+
+
+
+static void G_GNUC_UNUSED
+dump_framebuffer (const char *filename, int w, int h)
+{
+  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, w);
+  guchar *data = g_malloc (h * stride);
+  cairo_surface_t *s;
+
+  glReadPixels (0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, data);
+  s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24, w, h, stride);
+  cairo_surface_write_to_png (s, filename);
+
+  cairo_surface_destroy (s);
+  g_free (data);
+}
+
+static gboolean G_GNUC_UNUSED
+font_has_color_glyphs (const PangoFont *font)
+{
+  cairo_scaled_font_t *scaled_font;
+  gboolean has_color = FALSE;
+
+  scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font);
+  if (cairo_scaled_font_get_type (scaled_font) == CAIRO_FONT_TYPE_FT)
+    {
+      FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
+      has_color = (FT_HAS_COLOR (ft_face) != 0);
+      cairo_ft_scaled_font_unlock_face (scaled_font);
+    }
+
+  return has_color;
+}
+
+static void
+gsk_gl_renderer_setup_render_mode (GskGLRenderer *self);
+
+typedef struct
+{
+  int index;        /* Into the renderer's program array */
+  const char *name; /* For debugging */
+
+  int id;
+  /* Common locations (gl_common)*/
+  int source_location;
+  int mask_location;
+  int uv_location;
+  int position_location;
+  int alpha_location;
+  int blend_mode_location;
+  int viewport_location;
+  int projection_location;
+  int modelview_location;
+  int clip_location;
+  int clip_corner_widths_location;
+  int clip_corner_heights_location;
+
+  /* Program-specific locations */
+  union {
+    struct {
+      int color_location;
+    };
+    struct {
+      int color_matrix_location;
+      int color_offset_location;
+    };
+    struct {
+      int n_color_stops_location;
+      int color_stops_location;
+      int color_offsets_location;
+      int start_point_location;
+      int end_point_location;
+    };
+    struct {
+      int clip_bounds_location;
+      int corner_widths_location;
+      int corner_heights_location;
+    };
+  };
+} Program;
+
+enum {
+  MODE_BLIT = 1,
+  MODE_COLOR,
+  MODE_COLORING,
+  MODE_TEXTURE,
+  MODE_COLOR_MATRIX,
+  MODE_LINEAR_GRADIENT,
+  N_MODES
+};
+
+enum {
+  OP_NONE,
+  OP_CHANGE_OPACITY         =  1,
+  OP_CHANGE_COLOR           =  2,
+  OP_CHANGE_PROJECTION      =  3,
+  OP_CHANGE_MODELVIEW       =  4,
+  OP_CHANGE_PROGRAM         =  5,
+  OP_CHANGE_RENDER_TARGET   =  6,
+  OP_CHANGE_CLIP            =  7,
+  OP_CHANGE_VIEWPORT        =  8,
+  OP_CHANGE_SOURCE_TEXTURE  =  9,
+  OP_CHANGE_VAO             =  10,
+  OP_CHANGE_LINEAR_GRADIENT =  11,
+  OP_DRAW                   =  12,
+};
+
+typedef struct
+{
+  guint op;
+
+  union {
+    float opacity;
+    graphene_matrix_t modelview; // TODO: Make both matrix members just "matrix".
+    graphene_matrix_t projection;
+    const Program *program;
+    GdkRGBA color;
+    gsize vao_offset;
+    GskQuadVertex vertex_data[N_VERTICES]; // New Quad
+    int texture_id;
+    int render_target_id;
+    GskRoundedRect clip;
+    graphene_rect_t viewport;
+    struct {
+      int n_color_stops;
+      float color_offsets[8];
+      float color_stops[4 * 8];
+      graphene_point_t start_point;
+      graphene_point_t end_point;
+    } linear_gradient;
+  };
+} RenderOp;
+
+#ifdef G_ENABLE_DEBUG
+typedef struct
+{
+  GQuark frames;
+  GQuark draw_calls;
+} ProfileCounters;
+
+typedef struct
+{
+  GQuark cpu_time;
+  GQuark gpu_time;
+} ProfileTimers;
+#endif
+
+
+typedef enum
+{
+  RENDER_FULL,
+  RENDER_SCISSOR
+} RenderMode;
+
+struct _GskGLRenderer
+{
+  GskRenderer parent_instance;
+
+  int scale_factor;
+
+  graphene_rect_t viewport;
+
+  guint frame_buffer;
+  guint depth_stencil_buffer;
+  guint texture_id;
+
+
+  GdkGLContext *gl_context;
+  GskGLDriver *gl_driver;
+  GskGLProfiler *gl_profiler;
+
+  union {
+    Program programs[N_PROGRAMS];
+    struct {
+      Program blend_program;
+      Program blit_program;
+      Program color_program;
+      Program coloring_program;
+      Program color_matrix_program;
+      Program linear_gradient_program;
+    };
+  };
+
+  GArray *render_ops;
+
+  GskGLGlyphCache glyph_cache;
+  int full_vao_id;
+  int full_vao_buffer_id;
+
+#ifdef G_ENABLE_DEBUG
+  ProfileCounters profile_counters;
+  ProfileTimers profile_timers;
+#endif
+
+  RenderMode render_mode;
+
+  gboolean has_buffers : 1;
+};
+
+struct _GskGLRendererClass
+{
+  GskRendererClass parent_class;
+};
+
+G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
+
+typedef struct
+{
+  /* Per-Program State */
+  struct {
+    GskRoundedRect clip;
+    graphene_matrix_t modelview;
+    graphene_matrix_t projection;
+    int source_texture;
+    graphene_rect_t viewport;
+  } program_state[N_PROGRAMS];
+
+  /* Current global state */
+  const Program *current_program;
+  int current_render_target;
+  int current_vao;
+  int current_texture;
+  GskRoundedRect current_clip;
+  graphene_matrix_t current_modelview;
+  graphene_matrix_t current_projection;
+  graphene_rect_t current_viewport;
+  float current_opacity;
+
+  gsize buffer_size;
+
+  GskGLRenderer *renderer;
+} RenderOpBuilder;
+
+static void
+add_program_op (RenderOpBuilder *builder,
+                const Program   *new_program)
+{
+  static const GskRoundedRect empty_clip;
+  static const graphene_matrix_t empty_matrix;
+  static const graphene_rect_t empty_rect;
+  RenderOp op;
+
+  if (builder->current_program == new_program)
+    return;
+
+  op.op = OP_CHANGE_PROGRAM;
+  op.program = new_program;
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->current_program = new_program;
+
+  /* If the projection is not yet set for this program, we use the current one. */
+  if (memcmp (&empty_matrix, &builder->program_state[new_program->index].projection, sizeof (graphene_matrix_t)) == 0 ||
+      memcmp (&builder->current_projection, &builder->program_state[new_program->index].projection, sizeof (graphene_matrix_t)) != 0)
+    {
+      op.op = OP_CHANGE_PROJECTION;
+      op.projection = builder->current_projection;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].projection = builder->current_projection;
+    }
+
+  if (memcmp (&empty_matrix, &builder->program_state[new_program->index].modelview, sizeof (graphene_matrix_t)) == 0 ||
+      memcmp (&builder->current_modelview, &builder->program_state[new_program->index].modelview, sizeof (graphene_matrix_t)) != 0)
+    {
+      op.op = OP_CHANGE_MODELVIEW;
+      op.modelview = builder->current_modelview;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].modelview = builder->current_modelview;
+    }
+
+  if (memcmp (&empty_rect, &builder->program_state[new_program->index].viewport, sizeof (graphene_rect_t)) == 0 ||
+      memcmp (&builder->current_viewport, &builder->program_state[new_program->index].viewport, sizeof (graphene_rect_t)) != 0)
+    {
+      op.op = OP_CHANGE_VIEWPORT;
+      op.viewport = builder->current_viewport;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].viewport = builder->current_viewport;
+    }
+
+  if (memcmp (&empty_clip, &builder->program_state[new_program->index].clip, sizeof (GskRoundedRect)) == 0 ||
+      memcmp (&builder->current_clip, &builder->program_state[new_program->index].clip, sizeof (GskRoundedRect)) != 0)
+    {
+      op.op = OP_CHANGE_CLIP;
+      op.clip = builder->current_clip;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].clip = builder->current_clip;
+    }
+
+  if (graphene_rect_equal (&empty_rect, &builder->program_state[new_program->index].viewport) ||
+      !graphene_rect_equal (&builder->current_viewport, &builder->program_state[new_program->index].viewport))
+    {
+      op.op = OP_CHANGE_VIEWPORT;
+      op.viewport = builder->current_viewport;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].viewport = builder->current_viewport;
+    }
+}
+
+static GskRoundedRect
+add_clip_op (RenderOpBuilder      *builder,
+             const GskRoundedRect *new_clip)
+{
+  RenderOp op;
+  GskRoundedRect prev_clip;
+
+  op.op = OP_CHANGE_CLIP;
+  op.clip = *new_clip;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].clip = *new_clip;
+
+  prev_clip = builder->current_clip;
+  builder->current_clip = *new_clip;
+
+  return prev_clip;
+}
+
+static graphene_matrix_t
+add_modelview_op (RenderOpBuilder         *builder,
+                  const graphene_matrix_t *matrix)
+{
+  RenderOp op;
+  graphene_matrix_t prev_mv;
+  RenderOp *last_op;
+
+  last_op = &g_array_index (builder->renderer->render_ops, RenderOp, builder->renderer->render_ops->len - 1);
+  if (last_op->op == OP_CHANGE_MODELVIEW)
+    {
+      last_op->modelview = *matrix;
+    }
+  else
+    {
+      op.op = OP_CHANGE_MODELVIEW;
+      op.modelview = *matrix;
+      g_array_append_val (builder->renderer->render_ops, op);
+    }
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].modelview = *matrix;
+
+  prev_mv = builder->current_modelview;
+  builder->current_modelview = *matrix;
+
+  return prev_mv;
+}
+
+static graphene_matrix_t
+add_projection_op (RenderOpBuilder         *builder,
+                   const graphene_matrix_t *matrix)
+{
+  RenderOp op;
+  graphene_matrix_t prev_proj;
+
+  op.op = OP_CHANGE_PROJECTION;
+  op.projection = *matrix;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].projection = *matrix;
+
+  prev_proj = builder->current_projection;
+  builder->current_projection = *matrix;
+
+  return prev_proj;
+}
+
+static graphene_rect_t
+add_viewport_op (RenderOpBuilder       *builder,
+                 const graphene_rect_t *viewport)
+{
+  RenderOp op;
+  graphene_rect_t prev_viewport;
+
+  op.op = OP_CHANGE_VIEWPORT;
+  op.viewport = *viewport;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].viewport = *viewport;
+
+  prev_viewport = builder->current_viewport;
+  builder->current_viewport = *viewport;
+
+  return prev_viewport;
+}
+
+static void
+add_texture_op (RenderOpBuilder *builder,
+                int              texture_id)
+{
+  RenderOp op;
+
+  if (builder->current_texture == texture_id)
+    return;
+
+  op.op = OP_CHANGE_SOURCE_TEXTURE;
+  op.texture_id = texture_id;
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->current_texture = texture_id;
+}
+
+static float
+add_opacity_op (RenderOpBuilder *builder,
+                float            opacity)
+{
+  RenderOp op;
+  float prev_opacity;
+
+  if (builder->current_opacity == opacity)
+    return opacity;
+
+  op.op = OP_CHANGE_OPACITY;
+  op.opacity = opacity;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  prev_opacity = builder->current_opacity;
+  builder->current_opacity = opacity;
+
+  return prev_opacity;
+}
+
+static int
+add_render_target_op (RenderOpBuilder *builder,
+                      int              render_target_id)
+{
+  RenderOp op;
+  int prev_render_target;
+
+  if (builder->current_render_target == render_target_id)
+    return render_target_id;
+
+  prev_render_target = builder->current_render_target;
+  op.op = OP_CHANGE_RENDER_TARGET;
+  op.render_target_id = render_target_id;
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->current_render_target = render_target_id;
+
+  return prev_render_target;
+}
+
+static void
+add_draw_op (RenderOpBuilder     *builder,
+             const GskQuadVertex  vertex_data[N_VERTICES])
+{
+  RenderOp op;
+  gsize offset = builder->buffer_size / sizeof (GskQuadVertex);
+
+  op.op = OP_CHANGE_VAO;
+  memcpy (&op.vertex_data, vertex_data, sizeof(GskQuadVertex) * N_VERTICES);
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->buffer_size += sizeof (GskQuadVertex) * N_VERTICES;
+
+  op.op = OP_DRAW;
+  op.vao_offset = offset;
+  g_array_append_val (builder->renderer->render_ops, op);
+}
+
+static void
+add_op (RenderOpBuilder *builder,
+        const RenderOp  *op)
+{
+  g_array_append_val (builder->renderer->render_ops, *op);
+}
+
+static void
+gsk_gl_renderer_dispose (GObject *gobject)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (gobject);
+
+  g_clear_pointer (&self->render_ops, g_array_unref);
+
+  G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
+}
+
+static void
+gsk_gl_renderer_create_buffers (GskGLRenderer *self,
+                                int            width,
+                                int            height,
+                                int            scale_factor)
+{
+  if (self->has_buffers)
+    return;
+
+  GSK_NOTE (OPENGL, g_print ("Creating buffers (w:%d, h:%d, scale:%d)\n", width, height, scale_factor));
+
+  if (self->texture_id == 0)
+    {
+      self->texture_id = gsk_gl_driver_create_texture (self->gl_driver,
+                                                       width * scale_factor,
+                                                       height * scale_factor);
+      gsk_gl_driver_bind_source_texture (self->gl_driver, self->texture_id);
+      gsk_gl_driver_init_texture_empty (self->gl_driver, self->texture_id);
+    }
+
+  gsk_gl_driver_create_render_target (self->gl_driver, self->texture_id, TRUE, TRUE);
+  gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id);
+
+  self->has_buffers = TRUE;
+}
+
+static void
+gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
+{
+  if (self->gl_context == NULL)
+    return;
+
+  if (!self->has_buffers)
+    return;
+
+  GSK_NOTE (OPENGL, g_print ("Destroying buffers\n"));
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  if (self->texture_id != 0)
+    {
+      gsk_gl_driver_destroy_texture (self->gl_driver, self->texture_id);
+      self->texture_id = 0;
+    }
+
+  self->has_buffers = FALSE;
+}
+
+static void
+init_common_locations (GskGLRenderer    *self,
+                       GskShaderBuilder *builder,
+                       Program          *prog)
+{
+  prog->source_location = glGetUniformLocation (prog->id, "uSource");
+  prog->mask_location = glGetUniformLocation (prog->id, "uMask");
+  prog->alpha_location = glGetUniformLocation (prog->id, "uAlpha");
+  prog->blend_mode_location = glGetUniformLocation (prog->id, "uBlendMode");
+  prog->viewport_location = glGetUniformLocation (prog->id, "uViewport");
+  prog->projection_location = glGetUniformLocation (prog->id, "uProjection");
+  prog->modelview_location = glGetUniformLocation (prog->id, "uModelview");
+  prog->clip_location = glGetUniformLocation (prog->id, "uClip");
+  prog->clip_corner_widths_location = glGetUniformLocation (prog->id, "uClipCornerWidths");
+  prog->clip_corner_heights_location = glGetUniformLocation (prog->id, "uClipCornerHeights");
+
+  prog->position_location = glGetAttribLocation (prog->id, "aPosition");
+  prog->uv_location = glGetAttribLocation (prog->id, "aUv");
+}
+
+static gboolean
+gsk_gl_renderer_create_programs (GskGLRenderer  *self,
+                                 GError        **error)
+{
+  GskShaderBuilder *builder;
+  GError *shader_error = NULL;
+  gboolean res = FALSE;
+
+  builder = gsk_shader_builder_new ();
+
+  gsk_shader_builder_set_resource_base_path (builder, "/org/gtk/libgsk/glsl");
+
+  if (gdk_gl_context_get_use_es (self->gl_context))
+    {
+      gsk_shader_builder_set_version (builder, SHADER_VERSION_GLES);
+      gsk_shader_builder_set_vertex_preamble (builder, "es2_common.vs.glsl");
+      gsk_shader_builder_set_fragment_preamble (builder, "es2_common.fs.glsl");
+      gsk_shader_builder_add_define (builder, "GSK_GLES", "1");
+    }
+  else if (gdk_gl_context_is_legacy (self->gl_context))
+    {
+      int maj, min;
+      gdk_gl_context_get_version (self->gl_context, &maj, &min);
+
+      if (maj == 3)
+        gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3_LEGACY);
+      else
+        gsk_shader_builder_set_version (builder, SHADER_VERSION_GL2_LEGACY);
+
+      gsk_shader_builder_set_vertex_preamble (builder, "gl_common.vs.glsl");
+      gsk_shader_builder_set_fragment_preamble (builder, "gl_common.fs.glsl");
+      gsk_shader_builder_add_define (builder, "GSK_LEGACY", "1");
+    }
+  else
+    {
+      gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3);
+      gsk_shader_builder_set_vertex_preamble (builder, "gl3_common.vs.glsl");
+      gsk_shader_builder_set_fragment_preamble (builder, "gl3_common.fs.glsl");
+      gsk_shader_builder_add_define (builder, "GSK_GL3", "1");
+    }
+
+#ifdef G_ENABLE_DEBUG
+  if (GSK_RENDER_MODE_CHECK (SHADERS))
+    gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1");
+#endif
+
+  self->blend_program.id =  gsk_shader_builder_create_program (builder,
+                                                               "blend.vs.glsl", "blend.fs.glsl",
+                                                               &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'blend' program: ");
+      goto out;
+    }
+  self->blend_program.index = 0;
+  init_common_locations (self, builder, &self->blend_program);
+
+  self->blit_program.id = gsk_shader_builder_create_program (builder,
+                                                             "blit.vs.glsl", "blit.fs.glsl",
+                                                             &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'blit' program: ");
+      goto out;
+    }
+  self->blit_program.index = 1;
+  init_common_locations (self, builder, &self->blit_program);
+
+  self->color_program.id = gsk_shader_builder_create_program (builder,
+                                                              "blit.vs.glsl", "color.fs.glsl",
+                                                              &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'color' program: ");
+      goto out;
+    }
+  self->color_program.index = 2;
+  init_common_locations (self, builder, &self->color_program);
+  INIT_PROGRAM_UNIFORM_LOCATION (color_program, color_location, "uColor");
+
+  self->coloring_program.id = gsk_shader_builder_create_program (builder,
+                                                                 "blit.vs.glsl", "coloring.fs.glsl",
+                                                                 &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'coloring' program: ");
+      goto out;
+    }
+  self->coloring_program.index = 3;
+  init_common_locations (self, builder, &self->coloring_program);
+  INIT_PROGRAM_UNIFORM_LOCATION (coloring_program, color_location, "uColor");
+
+  self->color_matrix_program.id = gsk_shader_builder_create_program (builder,
+                                                                     "blit.vs.glsl", "color_matrix.fs.glsl",
+                                                                     &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'color_matrix' program: ");
+      goto out;
+    }
+  self->color_matrix_program.index = 4;
+  init_common_locations (self, builder, &self->color_matrix_program);
+  INIT_PROGRAM_UNIFORM_LOCATION (color_matrix_program, color_matrix_location, "uColorMatrix");
+  INIT_PROGRAM_UNIFORM_LOCATION (color_matrix_program, color_offset_location, "uColorOffset");
+
+  self->linear_gradient_program.id = gsk_shader_builder_create_program (builder,
+                                                                        "blit.vs.glsl", "linear_gradient.fs.glsl",
+                                                                        &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'linear_gradient' program: ");
+      goto out;
+    }
+  self->linear_gradient_program.index = 5;
+  init_common_locations (self, builder, &self->linear_gradient_program);
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, color_stops_location, "uColorStops");
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, color_offsets_location, "uColorOffsets");
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, n_color_stops_location, "uNumColorStops");
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, start_point_location, "uStartPoint");
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, end_point_location, "uEndPoint");
+
+  res = TRUE;
+
+out:
+
+  g_object_unref (builder);
+  return res;
+}
+
+static gboolean
+gsk_gl_renderer_realize (GskRenderer  *renderer,
+                         GdkWindow    *window,
+                         GError      **error)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  GskQuadVertex vertex_data[N_VERTICES] = {
+    { { 0, 0 }, { 0, 0 }, },
+    { { 0, 1 }, { 0, 1 }, },
+    { { 1, 0 }, { 1, 0 }, },
+
+    { { 1, 1 }, { 1, 1 }, },
+    { { 0, 1 }, { 0, 1 }, },
+    { { 1, 0 }, { 1, 0 }, },
+  };
+
+
+  self->scale_factor = gdk_window_get_scale_factor (window);
+
+  /* If we didn't get a GdkGLContext before realization, try creating
+   * one now, for our exclusive use.
+   */
+  if (self->gl_context == NULL)
+    {
+      self->gl_context = gdk_window_create_gl_context (window, error);
+      if (self->gl_context == NULL)
+        return FALSE;
+    }
+
+  if (!gdk_gl_context_realize (self->gl_context, error))
+    return FALSE;
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  g_assert (self->gl_driver == NULL);
+  self->gl_profiler = gsk_gl_profiler_new (self->gl_context);
+  self->gl_driver = gsk_gl_driver_new (self->gl_context);
+
+  GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n"));
+  if (!gsk_gl_renderer_create_programs (self, error))
+    return FALSE;
+
+  gsk_gl_glyph_cache_init (&self->glyph_cache, self->gl_driver);
+
+  gsk_gl_driver_create_permanent_vao_for_quad (self->gl_driver, N_VERTICES, vertex_data,
+                                               &self->full_vao_id, &self->full_vao_buffer_id);
+
+  return TRUE;
+}
+
+static void
+gsk_gl_renderer_unrealize (GskRenderer *renderer)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+
+  if (self->gl_context == NULL)
+    return;
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  /* We don't need to iterate to destroy the associated GL resources,
+   * as they will be dropped when we finalize the GskGLDriver
+   */
+  g_array_set_size (self->render_ops, 0);
+
+
+  glDeleteProgram (self->blend_program.id);
+  glDeleteProgram (self->blit_program.id);
+  glDeleteProgram (self->color_program.id);
+  glDeleteProgram (self->coloring_program.id);
+  glDeleteProgram (self->color_matrix_program.id);
+  glDeleteProgram (self->linear_gradient_program.id);
+
+  gsk_gl_renderer_destroy_buffers (self);
+
+  gsk_gl_glyph_cache_free (&self->glyph_cache);
+
+  g_clear_object (&self->gl_profiler);
+  g_clear_object (&self->gl_driver);
+
+  if (self->gl_context == gdk_gl_context_get_current ())
+    gdk_gl_context_clear_current ();
+
+  g_clear_object (&self->gl_context);
+}
+
+static GdkDrawingContext *
+gsk_gl_renderer_begin_draw_frame (GskRenderer          *renderer,
+                                  const cairo_region_t *update_area)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  cairo_region_t *damage;
+  GdkDrawingContext *result;
+  GdkRectangle whole_window;
+  GdkWindow *window;
+
+  window = gsk_renderer_get_window (renderer);
+  whole_window = (GdkRectangle) {
+                     0, 0,
+                     gdk_window_get_width (window) * self->scale_factor,
+                     gdk_window_get_height (window) * self->scale_factor
+                 };
+  damage = gdk_gl_context_get_damage (self->gl_context);
+  cairo_region_union (damage, update_area);
+
+  if (cairo_region_contains_rectangle (damage, &whole_window) == CAIRO_REGION_OVERLAP_IN)
+    {
+      self->render_mode = RENDER_FULL;
+    }
+  else
+    {
+      GdkRectangle extents;
+
+      cairo_region_get_extents (damage, &extents);
+      cairo_region_union_rectangle (damage, &extents);
+
+      if (gdk_rectangle_equal (&extents, &whole_window))
+        self->render_mode = RENDER_FULL;
+      else
+        self->render_mode = RENDER_SCISSOR;
+    }
+
+  result = gdk_window_begin_draw_frame (window,
+                                        GDK_DRAW_CONTEXT (self->gl_context),
+                                        damage);
+
+  cairo_region_destroy (damage);
+
+  return result;
+}
+
+static void
+gsk_gl_renderer_resize_viewport (GskGLRenderer         *self,
+                                 const graphene_rect_t *viewport)
+{
+  int width = viewport->size.width;
+  int height = viewport->size.height;
+
+  GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %d, %d) [scale:%d]\n",
+                             width,
+                             height,
+                             self->scale_factor));
+
+  graphene_rect_init (&self->viewport, 0, 0, width, height);
+  glViewport (0, 0, width, height);
+}
+
+
+static void
+get_gl_scaling_filters (GskRenderNode *node,
+                        int           *min_filter_r,
+                        int           *mag_filter_r)
+{
+  *min_filter_r = GL_NEAREST;
+  *mag_filter_r = GL_NEAREST;
+}
+
+static void
+gsk_gl_renderer_clear_tree (GskGLRenderer *self)
+{
+  int removed_textures, removed_vaos;
+
+  if (self->gl_context == NULL)
+    return;
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  g_array_remove_range (self->render_ops, 0, self->render_ops->len);
+
+  removed_textures = gsk_gl_driver_collect_textures (self->gl_driver);
+  removed_vaos = gsk_gl_driver_collect_vaos (self->gl_driver);
+
+  GSK_NOTE (OPENGL, g_print ("Collected: %d textures, %d vaos\n",
+                             removed_textures,
+                             removed_vaos));
+}
+
+static void
+gsk_gl_renderer_clear (GskGLRenderer *self)
+{
+  GSK_NOTE (OPENGL, g_print ("Clearing viewport\n"));
+  glClearColor (0, 0, 0, 0);
+  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+}
+
+static void
+gsk_gl_renderer_setup_render_mode (GskGLRenderer *self)
+{
+  switch (self->render_mode)
+  {
+    case RENDER_FULL:
+      glDisable (GL_SCISSOR_TEST);
+      break;
+
+    case RENDER_SCISSOR:
+      {
+        GdkDrawingContext *context = gsk_renderer_get_drawing_context (GSK_RENDERER (self));
+        GdkWindow *window = gsk_renderer_get_window (GSK_RENDERER (self));
+        cairo_region_t *clip = gdk_drawing_context_get_clip (context);
+        cairo_rectangle_int_t extents;
+        int window_height;
+
+        /* Fall back to RENDER_FULL */
+        if (clip == NULL)
+          {
+            glDisable (GL_SCISSOR_TEST);
+            return;
+          }
+
+        g_assert (cairo_region_num_rectangles (clip) == 1);
+
+        window_height = gdk_window_get_height (window) * self->scale_factor;
+
+        /*cairo_region_get_extents (clip, &extents);*/
+        cairo_region_get_rectangle (clip, 0, &extents);
+
+        glEnable (GL_SCISSOR_TEST);
+        glScissor (extents.x * self->scale_factor,
+                   window_height - (extents.height * self->scale_factor) - (extents.y * self->scale_factor),
+                   extents.width * self->scale_factor,
+                   extents.height * self->scale_factor);
+
+        cairo_region_destroy (clip);
+        break;
+      }
+
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+}
+
+
+static void
+gsk_gl_renderer_add_render_ops (GskGLRenderer   *self,
+                                GskRenderNode   *node,
+                                RenderOpBuilder *builder)
+{
+  float min_x = node->bounds.origin.x;
+  float min_y = node->bounds.origin.y;
+  float max_x = min_x + node->bounds.size.width;
+  float max_y = min_y + node->bounds.size.height;
+
+  /* Default vertex data */
+  GskQuadVertex vertex_data[N_VERTICES] = {
+    { { min_x, min_y }, { 0, 0 }, },
+    { { min_x, max_y }, { 0, 1 }, },
+    { { max_x, min_y }, { 1, 0 }, },
+
+    { { max_x, max_y }, { 1, 1 }, },
+    { { min_x, max_y }, { 0, 1 }, },
+    { { max_x, min_y }, { 1, 0 }, },
+  };
+
+  /*if (gsk_render_node_get_node_type (node) != GSK_CONTAINER_NODE)*/
+    /*g_message ("Adding ops for node %s with type %u", node->name,*/
+               /*gsk_render_node_get_node_type (node));*/
+
+
+  switch (gsk_render_node_get_node_type (node))
+    {
+    case GSK_NOT_A_RENDER_NODE:
+      g_assert_not_reached ();
+
+    case GSK_CONTAINER_NODE:
+      {
+        guint i, p;
+
+        for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i ++)
+          {
+            GskRenderNode *child = gsk_container_node_get_child (node, i);
+
+            gsk_gl_renderer_add_render_ops (self, child, builder);
+          }
+      }
+    break;
+
+    case GSK_COLOR_NODE:
+      {
+        RenderOp op;
+
+        add_program_op (builder, &self->color_program);
+        op.op = OP_CHANGE_COLOR;
+        op.color = *gsk_color_node_peek_color (node);
+        add_op (builder, &op);
+        add_draw_op (builder, vertex_data);
+      }
+    break;
+
+    case GSK_TEXTURE_NODE:
+      {
+        GdkTexture *texture = gsk_texture_node_get_texture (node);
+        int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
+        int texture_id;
+
+        get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
+
+        texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
+                                                            texture,
+                                                            gl_min_filter,
+                                                            gl_mag_filter);
+        add_program_op (builder, &self->blit_program);
+        add_texture_op (builder, texture_id);
+        add_draw_op (builder, vertex_data);
+      }
+    break;
+
+    case GSK_TRANSFORM_NODE:
+      {
+        GskRenderNode *child = gsk_transform_node_get_child (node);
+        graphene_matrix_t prev_mv;
+        graphene_matrix_t transform, transformed_mv;
+
+        graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node));
+        graphene_matrix_multiply (&transform, &builder->current_modelview, &transformed_mv);
+        prev_mv = add_modelview_op (builder, &transformed_mv);
+
+        gsk_gl_renderer_add_render_ops (self, child, builder);
+
+        add_modelview_op (builder, &prev_mv);
+      }
+    break;
+
+    case GSK_OPACITY_NODE:
+      {
+        int render_target;
+        int texture;
+        int prev_render_target;
+        float prev_opacity;
+        graphene_matrix_t identity;
+        graphene_matrix_t prev_projection;
+        graphene_matrix_t prev_modelview;
+        graphene_rect_t prev_viewport;
+        graphene_matrix_t item_proj;
+        GskQuadVertex vertex_data[N_VERTICES] = {
+          { { min_x, min_y }, { 0, 1 }, },
+          { { min_x, max_y }, { 0, 0 }, },
+          { { max_x, min_y }, { 1, 1 }, },
+
+          { { max_x, max_y }, { 1, 0 }, },
+          { { min_x, max_y }, { 0, 0 }, },
+          { { max_x, min_y }, { 1, 1 }, },
+        };
+
+        texture = gsk_gl_driver_create_texture (self->gl_driver,
+                                                node->bounds.size.width,
+                                                node->bounds.size.height);
+        gsk_gl_driver_bind_source_texture (self->gl_driver, texture);
+        gsk_gl_driver_init_texture_empty (self->gl_driver, texture);
+        render_target = gsk_gl_driver_create_render_target (self->gl_driver, texture, TRUE, TRUE);
+
+        graphene_matrix_init_ortho (&item_proj,
+                                    min_x, max_x,
+                                    min_y, max_y,
+                                    ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
+        graphene_matrix_scale (&item_proj, 1, -1, 1);
+        graphene_matrix_init_identity (&identity);
+
+        prev_render_target = add_render_target_op (builder, render_target);
+        prev_projection = add_projection_op (builder, &item_proj);
+        prev_modelview = add_modelview_op (builder, &identity);
+        prev_viewport = add_viewport_op (builder, &node->bounds);
+
+        gsk_gl_renderer_add_render_ops (self, gsk_opacity_node_get_child (node), builder);
+
+        add_viewport_op (builder, &prev_viewport);
+        add_modelview_op (builder, &prev_modelview);
+        add_projection_op (builder, &prev_projection);
+        add_render_target_op (builder, prev_render_target);
+
+        add_program_op (builder, &self->blit_program);
+        prev_opacity = add_opacity_op (builder, gsk_opacity_node_get_opacity (node));
+        add_texture_op (builder, texture);
+        add_draw_op (builder, vertex_data);
+        add_opacity_op (builder, prev_opacity);
+      }
+    break;
+
+    case GSK_LINEAR_GRADIENT_NODE:
+      {
+        RenderOp op;
+        int n_color_stops = MIN (8, gsk_linear_gradient_node_get_n_color_stops (node));
+        const GskColorStop *stops = gsk_linear_gradient_node_peek_color_stops (node);
+        const graphene_point_t *start = gsk_linear_gradient_node_peek_start (node);
+        const graphene_point_t *end = gsk_linear_gradient_node_peek_end (node);
+        int i;
+
+        for (i = 0; i < n_color_stops; i ++)
+          {
+            const GskColorStop *stop = stops + i;
+
+            op.linear_gradient.color_stops[(i * 4) + 0] = stop->color.red;
+            op.linear_gradient.color_stops[(i * 4) + 1] = stop->color.green;
+            op.linear_gradient.color_stops[(i * 4) + 2] = stop->color.blue;
+            op.linear_gradient.color_stops[(i * 4) + 3] = stop->color.alpha;
+            op.linear_gradient.color_offsets[i] = stop->offset;
+          }
+
+        add_program_op (builder, &self->linear_gradient_program);
+        op.op = OP_CHANGE_LINEAR_GRADIENT;
+        op.linear_gradient.n_color_stops = n_color_stops;
+        op.linear_gradient.start_point = *start;
+        op.linear_gradient.end_point = *end;
+        add_op (builder, &op);
+
+        add_draw_op (builder, vertex_data);
+      }
+    break;
+
+    case GSK_CLIP_NODE:
+      {
+        GskRoundedRect prev_clip;
+        GskRenderNode *child = gsk_clip_node_get_child (node);
+        graphene_rect_t transformed_clip;
+        graphene_rect_t intersection;
+        GskRoundedRect child_clip;
+
+        transformed_clip = *gsk_clip_node_peek_clip (node);
+        graphene_matrix_transform_bounds (&builder->current_modelview, &transformed_clip, &transformed_clip);
+
+        graphene_rect_intersection (&transformed_clip,
+                                    &builder->current_clip.bounds,
+                                    &intersection);
+
+        gsk_rounded_rect_init_from_rect (&child_clip, &intersection, 0.0f);
+
+        prev_clip = add_clip_op (builder, &child_clip);
+        gsk_gl_renderer_add_render_ops (self, child, builder);
+        add_clip_op (builder, &prev_clip);
+      }
+    break;
+
+    case GSK_ROUNDED_CLIP_NODE:
+      {
+        GskRoundedRect prev_clip;
+        GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
+        const GskRoundedRect *rounded_clip = gsk_rounded_clip_node_peek_clip (node);
+        graphene_rect_t transformed_clip;
+        graphene_rect_t intersection;
+        GskRoundedRect child_clip;
+
+        transformed_clip = rounded_clip->bounds;
+        graphene_matrix_transform_bounds (&builder->current_modelview, &transformed_clip, &transformed_clip);
+
+        graphene_rect_intersection (&transformed_clip, &builder->current_clip.bounds,
+                                    &intersection);
+        gsk_rounded_rect_init (&child_clip, &intersection,
+                               &rounded_clip->corner[0],
+                               &rounded_clip->corner[1],
+                               &rounded_clip->corner[2],
+                               &rounded_clip->corner[3]);
+
+        prev_clip = add_clip_op (builder, &child_clip);
+        gsk_gl_renderer_add_render_ops (self, child, builder);
+        add_clip_op (builder, &prev_clip);
+      }
+    break;
+
+    case GSK_TEXT_NODE:
+      {
+        const PangoFont *font = gsk_text_node_peek_font (node);
+        const PangoGlyphInfo *glyphs = gsk_text_node_peek_glyphs (node);
+        guint num_glyphs = gsk_text_node_get_num_glyphs (node);
+        int i;
+        int x_position = 0;
+        int x = gsk_text_node_get_x (node);
+        int y = gsk_text_node_get_y (node);
+
+        /* We use one quad per character, unlike the other nodes which
+         * use at most one quad altogether */
+        for (i = 0; i < num_glyphs; i++)
+          {
+            const PangoGlyphInfo *gi = &glyphs[i];
+            const GskGLCachedGlyph *glyph;
+            int glyph_x, glyph_y, glyph_w, glyph_h;
+            float tx, ty, tx2, ty2;
+            double cx;
+            double cy;
+
+            if (gi->glyph == PANGO_GLYPH_EMPTY ||
+                (gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG) > 0)
+              continue;
+
+            glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache,
+                                               TRUE,
+                                               (PangoFont *)font,
+                                               gi->glyph,
+                                               self->scale_factor);
+
+            /* e.g. whitespace */
+            if (glyph->draw_width <= 0 || glyph->draw_height <= 0)
+              {
+                x_position += gi->geometry.width;
+                continue;
+              }
+            cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
+            cy = (double)(gi->geometry.y_offset) / PANGO_SCALE;
+
+            /* If the font has color glyphs, we don't need to recolor anything */
+            if (font_has_color_glyphs (font))
+              {
+                add_program_op (builder, &self->blit_program);
+              }
+            else
+              {
+                RenderOp op;
+
+                add_program_op (builder, &self->coloring_program);
+
+                op.op = OP_CHANGE_COLOR;
+                op.color = *gsk_text_node_peek_color (node);
+                add_op (builder, &op);
+              }
+
+            add_texture_op (builder, gsk_gl_glyph_cache_get_glyph_image (&self->glyph_cache,
+                                                                         glyph)->texture_id);
+
+            {
+              tx  = glyph->tx;
+              ty  = glyph->ty;
+              tx2 = tx + glyph->tw;
+              ty2 = ty + glyph->th;
+
+              glyph_x = x + cx + glyph->draw_x;
+              glyph_y = y + cy + glyph->draw_y;
+              glyph_w = glyph->draw_width;
+              glyph_h = glyph->draw_height;
+
+              GskQuadVertex vertex_data[N_VERTICES] = {
+                { { glyph_x,           glyph_y           }, { tx,  ty  }, },
+                { { glyph_x,           glyph_y + glyph_h }, { tx,  ty2 }, },
+                { { glyph_x + glyph_w, glyph_y           }, { tx2, ty  }, },
+
+                { { glyph_x + glyph_w, glyph_y + glyph_h }, { tx2, ty2 }, },
+                { { glyph_x,           glyph_y + glyph_h }, { tx,  ty2 }, },
+                { { glyph_x + glyph_w, glyph_y           }, { tx2, ty  }, },
+              };
+
+              add_draw_op (builder, vertex_data);
+            }
+
+            x_position += gi->geometry.width;
+          }
+
+      }
+    break;
+
+
+    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
+    case GSK_BORDER_NODE:
+    case GSK_INSET_SHADOW_NODE:
+    case GSK_OUTSET_SHADOW_NODE:
+    case GSK_BLUR_NODE:
+    case GSK_SHADOW_NODE:
+    case GSK_CROSS_FADE_NODE:
+    case GSK_BLEND_NODE:
+    case GSK_REPEAT_NODE:
+    case GSK_CAIRO_NODE:
+    case GSK_COLOR_MATRIX_NODE:
+    default:
+      {
+        cairo_surface_t *surface;
+        cairo_t *cr;
+        int texture_id;
+
+        surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                              ceilf (node->bounds.size.width) * self->scale_factor,
+                                              ceilf (node->bounds.size.height) * self->scale_factor);
+        cairo_surface_set_device_scale (surface, self->scale_factor, self->scale_factor);
+        cr = cairo_create (surface);
+
+        cairo_save (cr);
+        cairo_translate (cr, -min_x, -min_y);
+        gsk_render_node_draw (node, cr);
+        cairo_restore (cr);
+
+#if HIGHLIGHT_FALLBACK
+        cairo_move_to (cr, 0, 0);
+        cairo_rectangle (cr, 0, 0, max_x - min_x, max_y - min_y);
+        cairo_set_source_rgba (cr, 1, 0, 0, 1);
+        cairo_stroke (cr);
+#endif
+        cairo_destroy (cr);
+
+        /* Upload the Cairo surface to a GL texture */
+        texture_id = gsk_gl_driver_create_texture (self->gl_driver,
+                                                   node->bounds.size.width * self->scale_factor,
+                                                   node->bounds.size.height * self->scale_factor);
+
+        gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id);
+        gsk_gl_driver_init_texture_with_surface (self->gl_driver,
+                                                 texture_id,
+                                                 surface,
+                                                 GL_NEAREST, GL_NEAREST);
+
+        cairo_surface_destroy (surface);
+
+        add_program_op (builder, &self->blit_program);
+        add_texture_op (builder, texture_id);
+        add_draw_op (builder, vertex_data);
+      }
+    }
+}
+
+static void
+gsk_gl_renderer_render_ops (GskGLRenderer *self,
+                            gsize          vertex_data_size)
+{
+  guint i;
+  guint n_ops = self->render_ops->len;
+  float mat[16];
+  const Program *program = NULL;
+  gsize buffer_index = 0;
+  float *vertex_data = g_malloc (vertex_data_size);
+
+  /*g_message ("%s: Buffer size: %ld", __FUNCTION__, vertex_data_size);*/
+
+
+  GLuint buffer_id, vao_id;
+  glGenVertexArrays (1, &vao_id);
+  glBindVertexArray (vao_id);
+
+  glGenBuffers (1, &buffer_id);
+  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
+
+
+  // Fill buffer data
+  for (i = 0; i < n_ops; i ++)
+    {
+      const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
+
+      if (op->op == OP_CHANGE_VAO)
+        {
+          memcpy (vertex_data + buffer_index, &op->vertex_data, sizeof (GskQuadVertex) * N_VERTICES);
+          buffer_index += sizeof (GskQuadVertex) * N_VERTICES / sizeof (float);
+        }
+    }
+
+  // Set buffer data
+  glBufferData (GL_ARRAY_BUFFER, vertex_data_size, vertex_data, GL_STATIC_DRAW);
+
+  // Describe buffer contents
+
+  /* 0 = position location */
+  glEnableVertexAttribArray (0);
+  glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskQuadVertex),
+                         (void *) G_STRUCT_OFFSET (GskQuadVertex, position));
+  /* 1 = texture coord location */
+  glEnableVertexAttribArray (1);
+  glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskQuadVertex),
+                         (void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
+
+  for (i = 0; i < n_ops; i ++)
+    {
+      const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
+
+      if (op->op == OP_NONE ||
+          op->op == OP_CHANGE_VAO)
+        continue;
+
+      OP_PRINT ("Op %u: %u", i, op->op);
+
+      switch (op->op)
+        {
+        case OP_CHANGE_PROJECTION:
+          graphene_matrix_to_float (&op->projection, mat);
+          glUniformMatrix4fv (program->projection_location, 1, GL_FALSE, mat);
+          OP_PRINT (" -> Projection");
+          /*graphene_matrix_print (&op->projection);*/
+          break;
+
+        case OP_CHANGE_MODELVIEW:
+          graphene_matrix_to_float (&op->modelview, mat);
+          glUniformMatrix4fv (program->modelview_location, 1, GL_FALSE, mat);
+          OP_PRINT (" -> Modelview");
+          /*graphene_matrix_print (&op->modelview);*/
+          break;
+
+        case OP_CHANGE_PROGRAM:
+          program = op->program;
+          glUseProgram (op->program->id);
+          OP_PRINT (" -> Program: %d", op->program->id);
+          break;
+
+        case OP_CHANGE_RENDER_TARGET:
+          OP_PRINT (" -> Render Target: %d", op->render_target_id);
+
+          glBindFramebuffer (GL_FRAMEBUFFER, op->render_target_id);
+          if (op->render_target_id != 0)
+            {
+              glDisable (GL_SCISSOR_TEST);
+              glClearColor (0.0, 0.0, 0.0, 0.0);
+              glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+            }
+          else
+            {
+              /* Reset glScissor, etc. */
+              gsk_gl_renderer_setup_render_mode (self);
+            }
+          break;
+
+        case OP_CHANGE_VIEWPORT:
+          OP_PRINT (" -> New Viewport: %f, %f, %f, %f", op->viewport.origin.x, op->viewport.origin.y, op->viewport.size.width, op->viewport.size.height);
+          glUniform4f (program->viewport_location,
+                       op->viewport.origin.x, op->viewport.origin.y,
+                       op->viewport.size.width, op->viewport.size.height);
+          glViewport (0, 0, op->viewport.size.width, op->viewport.size.height);
+          break;
+
+        case OP_CHANGE_OPACITY:
+          glUniform1f (program->alpha_location, op->opacity);
+          break;
+
+        case OP_CHANGE_COLOR:
+          OP_PRINT (" -> Color: (%f, %f, %f, %f)", op->color.red, op->color.green, op->color.blue, op->color.alpha);
+          g_assert (program == &self->color_program || program == &self->coloring_program);
+          glUniform4f (program->color_location,
+                       op->color.red, op->color.green, op->color.blue, op->color.alpha);
+          break;
+
+        case OP_CHANGE_CLIP:
+          OP_PRINT (" -> Clip");
+          glUniform4f (program->clip_location,
+                       op->clip.bounds.origin.x, op->clip.bounds.origin.y,
+                       op->clip.bounds.size.width, op->clip.bounds.size.height);
+
+          glUniform4f (program->clip_corner_widths_location,
+                       MAX (op->clip.corner[0].width, 1),
+                       MAX (op->clip.corner[1].width, 1),
+                       MAX (op->clip.corner[2].width, 1),
+                       MAX (op->clip.corner[3].width, 1));
+          glUniform4f (program->clip_corner_heights_location,
+                       MAX (op->clip.corner[0].height, 1),
+                       MAX (op->clip.corner[1].height, 1),
+                       MAX (op->clip.corner[2].height, 1),
+                       MAX (op->clip.corner[3].height, 1));
+          break;
+
+        case OP_CHANGE_SOURCE_TEXTURE:
+          g_assert(op->texture_id != 0);
+          OP_PRINT (" -> New texture: %d", op->texture_id);
+          /* Use texture unit 0 for the source */
+          glUniform1i (program->source_location, 0);
+          glActiveTexture (GL_TEXTURE0);
+          glBindTexture (GL_TEXTURE_2D, op->texture_id);
+
+          break;
+
+        case OP_CHANGE_LINEAR_GRADIENT:
+            OP_PRINT (" -> Linear gradient");
+            glUniform1i (program->n_color_stops_location,
+                         op->linear_gradient.n_color_stops);
+            glUniform4fv (program->color_stops_location,
+                          op->linear_gradient.n_color_stops,
+                          op->linear_gradient.color_stops);
+            glUniform1fv (program->color_offsets_location,
+                          op->linear_gradient.n_color_stops,
+                          op->linear_gradient.color_offsets);
+            glUniform2f (program->start_point_location,
+                         op->linear_gradient.start_point.x, op->linear_gradient.start_point.y);
+            glUniform2f (program->end_point_location,
+                         op->linear_gradient.end_point.x, op->linear_gradient.end_point.y);
+          break;
+
+        case OP_DRAW:
+          OP_PRINT (" -> draw %ld\n", op->vao_offset);
+          glDrawArrays (GL_TRIANGLES, op->vao_offset, N_VERTICES);
+          break;
+
+        default:
+          g_warn_if_reached ();
+        }
+
+      OP_PRINT ("\n");
+    }
+
+  /* Done drawing, destroy the buffer again.
+   * TODO: Can we reuse the memory, though? */
+  g_free (vertex_data);
+}
+
+static void
+gsk_gl_renderer_do_render (GskRenderer           *renderer,
+                           GskRenderNode         *root,
+                           const graphene_rect_t *viewport,
+                           int                    scale_factor)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  RenderOpBuilder render_op_builder;
+  graphene_matrix_t modelview, projection;
+#ifdef G_ENABLE_DEBUG
+  GskProfiler *profiler;
+  gint64 gpu_time, cpu_time;
+#endif
+
+#ifdef G_ENABLE_DEBUG
+  profiler = gsk_renderer_get_profiler (renderer);
+#endif
+
+  if (self->gl_context == NULL)
+    {
+      GSK_NOTE (OPENGL, g_print ("No valid GL context associated to the renderer"));
+      return;
+    }
+
+  self->viewport = *viewport;
+
+  /* Set up the modelview and projection matrices to fit our viewport */
+  graphene_matrix_init_scale (&modelview, scale_factor, scale_factor, 1.0);
+  graphene_matrix_init_ortho (&projection,
+                              viewport->origin.x,
+                              viewport->origin.x + viewport->size.width,
+                              viewport->origin.y,
+                              viewport->origin.y + viewport->size.height,
+                              ORTHO_NEAR_PLANE,
+                              ORTHO_FAR_PLANE);
+
+  if (self->texture_id == 0)
+    graphene_matrix_scale (&projection, 1, -1, 1);
+
+  gsk_gl_driver_begin_frame (self->gl_driver);
+  gsk_gl_glyph_cache_begin_frame (&self->glyph_cache);
+
+  memset (&render_op_builder, 0, sizeof (render_op_builder));
+  render_op_builder.renderer = self;
+  render_op_builder.current_projection = projection;
+  render_op_builder.current_modelview = modelview;
+  render_op_builder.current_viewport = *viewport;
+  render_op_builder.current_render_target = self->texture_id;
+  render_op_builder.current_opacity = 1.0f;
+  gsk_rounded_rect_init_from_rect (&render_op_builder.current_clip, &self->viewport, 0.0f);
+  gsk_gl_renderer_add_render_ops (self, root, &render_op_builder);
+
+  /*g_message ("Ops: %u", self->render_ops->len);*/
+
+  /* Now actually draw things... */
+#ifdef G_ENABLE_DEBUG
+  gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
+  gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
+#endif
+
+  gsk_gl_renderer_resize_viewport (self, viewport);
+  gsk_gl_renderer_setup_render_mode (self);
+  gsk_gl_renderer_clear (self);
+
+  glEnable (GL_DEPTH_TEST);
+  glDepthFunc (GL_LEQUAL);
+
+  /* Pre-multiplied alpha! */
+  glEnable (GL_BLEND);
+  glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+  glBlendEquation (GL_FUNC_ADD);
+
+  gsk_gl_renderer_render_ops (self, render_op_builder.buffer_size);
+
+  gsk_gl_driver_end_frame (self->gl_driver);
+
+#ifdef G_ENABLE_DEBUG
+  gsk_profiler_counter_inc (profiler, self->profile_counters.frames);
+
+  cpu_time = gsk_profiler_timer_end (profiler, self->profile_timers.cpu_time);
+  gsk_profiler_timer_set (profiler, self->profile_timers.cpu_time, cpu_time);
+
+  gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler);
+  gsk_profiler_timer_set (profiler, self->profile_timers.gpu_time, gpu_time);
+
+  gsk_profiler_push_samples (profiler);
+#endif
+}
+
+static GdkTexture *
+gsk_gl_renderer_render_texture (GskRenderer           *renderer,
+                                GskRenderNode         *root,
+                                const graphene_rect_t *viewport)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  GdkTexture *texture;
+  int stride;
+  guchar *data;
+  int width, height;
+
+  g_return_val_if_fail (self->gl_context != NULL, NULL);
+
+  self->render_mode = RENDER_FULL;
+  width = ceilf (viewport->size.width);
+  height = ceilf (viewport->size.height);
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  /* Prepare our framebuffer */
+  gsk_gl_driver_begin_frame (self->gl_driver);
+  gsk_gl_renderer_create_buffers (self, width, height, 1);
+  gsk_gl_renderer_clear (self);
+  gsk_gl_driver_end_frame (self->gl_driver);
+
+  /* Render the actual scene */
+  gsk_gl_renderer_do_render (renderer, root, viewport, 1);
+
+  /* Prepare memory for the glReadPixels call */
+  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
+  data = g_malloc (height * stride);
+
+  /* Bind our framebuffer again and read from it */
+  gsk_gl_driver_begin_frame (self->gl_driver);
+  gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id);
+  glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+  gsk_gl_driver_end_frame (self->gl_driver);
+
+  /* Create texture from the downloaded data */
+  texture = gdk_texture_new_for_data (data, width, height, stride);
+
+  return texture;
+}
+
+static void
+gsk_gl_renderer_render (GskRenderer   *renderer,
+                        GskRenderNode *root)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  GdkWindow *window = gsk_renderer_get_window (renderer);
+  graphene_rect_t viewport;
+
+  if (self->gl_context == NULL)
+    return;
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  viewport.origin.x = 0;
+  viewport.origin.y = 0;
+  viewport.size.width = gdk_window_get_width (window) * self->scale_factor;
+  viewport.size.height = gdk_window_get_height (window) * self->scale_factor;
+
+  gsk_gl_renderer_do_render (renderer, root, &viewport, self->scale_factor);
+
+  gdk_gl_context_make_current (self->gl_context);
+  gsk_gl_renderer_clear_tree (self);
+  gsk_gl_renderer_destroy_buffers (self);
+}
+
+static void
+gsk_gl_renderer_class_init (GskGLRendererClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
+
+  gobject_class->dispose = gsk_gl_renderer_dispose;
+
+  renderer_class->realize = gsk_gl_renderer_realize;
+  renderer_class->unrealize = gsk_gl_renderer_unrealize;
+  renderer_class->begin_draw_frame = gsk_gl_renderer_begin_draw_frame;
+  renderer_class->render = gsk_gl_renderer_render;
+  renderer_class->render_texture = gsk_gl_renderer_render_texture;
+}
+
+static void
+gsk_gl_renderer_init (GskGLRenderer *self)
+{
+  gsk_ensure_resources ();
+
+
+  self->scale_factor = 1;
+  self->render_ops = g_array_new (TRUE, FALSE, sizeof (RenderOp));
+
+#ifdef G_ENABLE_DEBUG
+  {
+    GskProfiler *profiler = gsk_renderer_get_profiler (GSK_RENDERER (self));
+
+    self->profile_counters.frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE);
+    self->profile_counters.draw_calls = gsk_profiler_add_counter (profiler, "draws", "glDrawArrays", TRUE);
+
+    self->profile_timers.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU time", FALSE, TRUE);
+    self->profile_timers.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU time", FALSE, TRUE);
+  }
+#endif
+}
diff --git a/gsk/gl/gskglrendererprivate.h b/gsk/gl/gskglrendererprivate.h
new file mode 100644 (file)
index 0000000..a30b201
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef __GSK_GL_RENDERER_PRIVATE_H__
+#define __GSK_GL_RENDERER_PRIVATE_H__
+
+#include <gsk/gskrenderer.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_RENDERER (gsk_gl_renderer_get_type ())
+
+#define GSK_GL_RENDERER(obj)                    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_RENDERER, GskGLRenderer))
+#define GSK_IS_GL_RENDERER(obj)                 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_RENDERER))
+#define GSK_GL_RENDERER_CLASS(klass)            (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
+#define GSK_IS_GL_RENDERER_CLASS(klass)         (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_RENDERER))
+#define GSK_GL_RENDERER_GET_CLASS(obj)          (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
+
+typedef struct _GskGLRenderer                   GskGLRenderer;
+typedef struct _GskGLRendererClass              GskGLRendererClass;
+
+GType gsk_gl_renderer_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GSK_GL_RENDERER_PRIVATE_H__ */
diff --git a/gsk/gskgldriver.c b/gsk/gskgldriver.c
deleted file mode 100644 (file)
index 9b49ebe..0000000
+++ /dev/null
@@ -1,950 +0,0 @@
-#include "config.h"
-
-#include "gskgldriverprivate.h"
-
-#include "gskdebugprivate.h"
-#include "gskprofilerprivate.h"
-#include "gdk/gdktextureprivate.h"
-
-#include <gdk/gdk.h>
-#include <epoxy/gl.h>
-
-typedef struct {
-  GLuint texture_id;
-  int width;
-  int height;
-  GLuint min_filter;
-  GLuint mag_filter;
-  GArray *fbos;
-  GdkTexture *user;
-  guint in_use : 1;
-  guint permanent : 1;
-} Texture;
-
-typedef struct {
-  GLuint vao_id;
-  GLuint buffer_id;
-  GLuint position_id;
-  GLuint uv_id;
-  GskQuadVertex *quads;
-  int n_quads;
-  guint in_use : 1;
-  guint permanent : 1;
-} Vao;
-
-typedef struct {
-  GLuint fbo_id;
-  GLuint depth_stencil_id;
-} Fbo;
-
-struct _GskGLDriver
-{
-  GObject parent_instance;
-
-  GdkGLContext *gl_context;
-  GskProfiler *profiler;
-  struct {
-    GQuark created_textures;
-    GQuark reused_textures;
-    GQuark surface_uploads;
-  } counters;
-
-  Fbo default_fbo;
-
-  GHashTable *textures;
-  GHashTable *vaos;
-
-  const Texture *bound_source_texture;
-  const Texture *bound_mask_texture;
-  const Vao *bound_vao;
-  const Fbo *bound_fbo;
-
-  int max_texture_size;
-
-  gboolean in_frame : 1;
-};
-
-enum
-{
-  PROP_GL_CONTEXT = 1,
-
-  N_PROPS
-};
-
-static GParamSpec *gsk_gl_driver_properties[N_PROPS];
-
-G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT)
-
-static Texture *
-texture_new (void)
-{
-  return g_slice_new0 (Texture);
-}
-
-static void
-texture_free (gpointer data)
-{
-  Texture *t = data;
-
-  if (t->user)
-    gdk_texture_clear_render_data (t->user);
-
-  g_clear_pointer (&t->fbos, g_array_unref);
-  glDeleteTextures (1, &t->texture_id);
-  g_slice_free (Texture, t);
-}
-
-static void
-fbo_clear (gpointer data)
-{
-  Fbo *f = data;
-
-  if (f->depth_stencil_id != 0)
-    glDeleteRenderbuffers (1, &f->depth_stencil_id);
-
-  glDeleteFramebuffers (1, &f->fbo_id);
-}
-
-static Vao *
-vao_new (void)
-{
-  return g_slice_new0 (Vao);
-}
-
-static void
-vao_free (gpointer data)
-{
-  Vao *v = data;
-
-  g_free (v->quads);
-  glDeleteBuffers (1, &v->buffer_id);
-  glDeleteVertexArrays (1, &v->vao_id);
-  g_slice_free (Vao, v);
-}
-
-static void
-gsk_gl_driver_finalize (GObject *gobject)
-{
-  GskGLDriver *self = GSK_GL_DRIVER (gobject);
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  g_clear_pointer (&self->textures, g_hash_table_unref);
-  g_clear_pointer (&self->vaos, g_hash_table_unref);
-  g_clear_object (&self->profiler);
-
-  if (self->gl_context == gdk_gl_context_get_current ())
-    gdk_gl_context_clear_current ();
-
-  g_clear_object (&self->gl_context);
-
-  G_OBJECT_CLASS (gsk_gl_driver_parent_class)->finalize (gobject);
-}
-
-static void
-gsk_gl_driver_set_property (GObject      *gobject,
-                            guint         prop_id,
-                            const GValue *value,
-                            GParamSpec   *pspec)
-{
-  GskGLDriver *self = GSK_GL_DRIVER (gobject);
-
-  switch (prop_id)
-    {
-    case PROP_GL_CONTEXT:
-      self->gl_context = g_value_dup_object (value);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
-    }
-}
-
-static void
-gsk_gl_driver_get_property (GObject    *gobject,
-                            guint       prop_id,
-                            GValue     *value,
-                            GParamSpec *pspec)
-{
-  GskGLDriver *self = GSK_GL_DRIVER (gobject);
-
-  switch (prop_id)
-    {
-    case PROP_GL_CONTEXT:
-      g_value_set_object (value, self->gl_context);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
-    }
-}
-
-static void
-gsk_gl_driver_class_init (GskGLDriverClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-  gobject_class->set_property = gsk_gl_driver_set_property;
-  gobject_class->get_property = gsk_gl_driver_get_property;
-  gobject_class->finalize = gsk_gl_driver_finalize;
-
-  gsk_gl_driver_properties[PROP_GL_CONTEXT] =
-    g_param_spec_object ("gl-context", "GL Context", "The GL context used by the driver",
-                         GDK_TYPE_GL_CONTEXT,
-                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-  g_object_class_install_properties (gobject_class, N_PROPS, gsk_gl_driver_properties);
-}
-
-static void
-gsk_gl_driver_init (GskGLDriver *self)
-{
-  self->textures = g_hash_table_new_full (NULL, NULL, NULL, texture_free);
-  self->vaos = g_hash_table_new_full (NULL, NULL, NULL, vao_free);
-
-  self->max_texture_size = -1;
-
-#ifdef G_ENABLE_DEBUG
-  self->profiler = gsk_profiler_new ();
-  self->counters.created_textures = gsk_profiler_add_counter (self->profiler,
-                                                              "created_textures",
-                                                              "Textures created this frame",
-                                                              TRUE);
-  self->counters.reused_textures = gsk_profiler_add_counter (self->profiler,
-                                                             "reused_textures",
-                                                             "Textures reused this frame",
-                                                             TRUE);
-  self->counters.surface_uploads = gsk_profiler_add_counter (self->profiler,
-                                                             "surface_uploads",
-                                                             "Texture uploads from surfaces this frame",
-                                                             TRUE);
-#endif
-}
-
-GskGLDriver *
-gsk_gl_driver_new (GdkGLContext *context)
-{
-  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
-
-  return g_object_new (GSK_TYPE_GL_DRIVER,
-                       "gl-context", context,
-                       NULL);
-}
-
-void
-gsk_gl_driver_begin_frame (GskGLDriver *self)
-{
-  g_return_if_fail (GSK_IS_GL_DRIVER (self));
-  g_return_if_fail (!self->in_frame);
-
-  self->in_frame = TRUE;
-
-  if (self->max_texture_size < 0)
-    {
-      glGetIntegerv (GL_MAX_TEXTURE_SIZE, (GLint *) &self->max_texture_size);
-      GSK_NOTE (OPENGL, g_print ("GL max texture size: %d\n", self->max_texture_size));
-    }
-
-  glBindFramebuffer (GL_FRAMEBUFFER, 0);
-  self->bound_fbo = &self->default_fbo;
-
-  glActiveTexture (GL_TEXTURE0);
-  glBindTexture (GL_TEXTURE_2D, 0);
-
-  glActiveTexture (GL_TEXTURE0 + 1);
-  glBindTexture (GL_TEXTURE_2D, 0);
-
-  glBindVertexArray (0);
-  glUseProgram (0);
-
-  glActiveTexture (GL_TEXTURE0);
-
-  gsk_profiler_reset (self->profiler);
-}
-
-void
-gsk_gl_driver_end_frame (GskGLDriver *self)
-{
-  g_return_if_fail (GSK_IS_GL_DRIVER (self));
-  g_return_if_fail (self->in_frame);
-
-  self->bound_source_texture = NULL;
-  self->bound_mask_texture = NULL;
-  self->bound_vao = NULL;
-  self->bound_fbo = NULL;
-
-  self->default_fbo.fbo_id = 0;
-
-  GSK_NOTE (OPENGL,
-            g_print ("Textures created: %ld\n"
-                     " Textures reused: %ld\n"
-                     " Surface uploads: %ld\n",
-                     gsk_profiler_counter_get (self->profiler, self->counters.created_textures),
-                     gsk_profiler_counter_get (self->profiler, self->counters.reused_textures),
-                     gsk_profiler_counter_get (self->profiler, self->counters.surface_uploads)));
-  GSK_NOTE (OPENGL,
-            g_print ("*** Frame end: textures=%d, vaos=%d\n",
-                     g_hash_table_size (self->textures),
-                     g_hash_table_size (self->vaos)));
-
-  self->in_frame = FALSE;
-}
-
-int
-gsk_gl_driver_collect_textures (GskGLDriver *driver)
-{
-  GHashTableIter iter;
-  gpointer value_p = NULL;
-  int old_size;
-  /*return;*/
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), 0);
-  g_return_val_if_fail (!driver->in_frame, 0);
-
-  old_size = g_hash_table_size (driver->textures);
-
-  g_hash_table_iter_init (&iter, driver->textures);
-  while (g_hash_table_iter_next (&iter, NULL, &value_p))
-    {
-      Texture *t = value_p;
-
-      if (t->user || t->permanent)
-        continue;
-
-      if (t->in_use)
-        {
-          t->in_use = FALSE;
-          g_clear_pointer (&t->fbos, g_array_unref);
-        }
-      else
-        g_hash_table_iter_remove (&iter);
-    }
-
-  return old_size - g_hash_table_size (driver->textures);
-}
-
-int
-gsk_gl_driver_collect_vaos (GskGLDriver *driver)
-{
-  GHashTableIter iter;
-  gpointer value_p = NULL;
-  int old_size;
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), 0);
-  g_return_val_if_fail (!driver->in_frame, 0);
-
-  old_size = g_hash_table_size (driver->vaos);
-
-  g_hash_table_iter_init (&iter, driver->vaos);
-  while (g_hash_table_iter_next (&iter, NULL, &value_p))
-    {
-      Vao *v = value_p;
-
-      if (v->in_use)
-        v->in_use = FALSE;
-      else
-        g_hash_table_iter_remove (&iter);
-    }
-
-  return old_size - g_hash_table_size (driver->vaos);
-}
-
-int
-gsk_gl_driver_get_max_texture_size (GskGLDriver *driver)
-{
-  if (driver->max_texture_size < 0)
-    {
-      if (gdk_gl_context_get_use_es (driver->gl_context))
-        return 2048;
-
-      return 1024;
-    }
-
-  return driver->max_texture_size;
-}
-
-static Texture *
-gsk_gl_driver_get_texture (GskGLDriver *driver,
-                           int          texture_id)
-{
-  Texture *t;
-
-  if (g_hash_table_lookup_extended (driver->textures, GINT_TO_POINTER (texture_id), NULL, (gpointer *) &t))
-    return t;
-
-  return NULL;
-}
-
-static Vao *
-gsk_gl_driver_get_vao (GskGLDriver *driver,
-                       int          vao_id)
-{
-  Vao *v;
-
-  if (g_hash_table_lookup_extended (driver->vaos, GINT_TO_POINTER (vao_id), NULL, (gpointer *) &v))
-    return v;
-
-  return NULL;
-}
-
-static Fbo *
-gsk_gl_driver_get_fbo (GskGLDriver *driver,
-                       int          texture_id)
-{
-  Texture *t = gsk_gl_driver_get_texture (driver, texture_id);
-
-  if (t->fbos == NULL)
-    return &driver->default_fbo;
-
-  return &g_array_index (t->fbos, Fbo, 0);
-}
-
-static Texture *
-find_texture_by_size (GHashTable *textures,
-                      int         width,
-                      int         height)
-{
-  GHashTableIter iter;
-  gpointer value_p = NULL;
-
-  g_hash_table_iter_init (&iter, textures);
-  while (g_hash_table_iter_next (&iter, NULL, &value_p))
-    {
-      Texture *t = value_p;
-
-      if (t->width == width && t->height == height)
-        return t;
-    }
-
-  return NULL;
-}
-
-static Texture *
-create_texture (GskGLDriver *self,
-                float        fwidth,
-                float        fheight)
-{
-  guint texture_id;
-  Texture *t;
-  int width = ceilf (fwidth);
-  int height = ceilf (fheight);
-
-  if (width >= self->max_texture_size ||
-      height >= self->max_texture_size)
-    {
-      g_critical ("Texture %d x %d is bigger than supported texture limit of %d; clipping...",
-                  width, height,
-                  self->max_texture_size);
-
-      width = MIN (width, self->max_texture_size);
-      height = MIN (height, self->max_texture_size);
-    }
-
-  t = find_texture_by_size (self->textures, width, height);
-  if (t != NULL && !t->in_use && t->user == NULL)
-    {
-      GSK_NOTE (OPENGL, g_print ("Reusing Texture(%d) for size %dx%d\n",
-                                 t->texture_id, t->width, t->height));
-      t->in_use = TRUE;
-      gsk_profiler_counter_inc (self->profiler, self->counters.reused_textures);
-      return t;
-    }
-
-  glGenTextures (1, &texture_id);
-
-  t = texture_new ();
-  t->texture_id = texture_id;
-  t->width = width;
-  t->height = height;
-  t->min_filter = GL_NEAREST;
-  t->mag_filter = GL_NEAREST;
-  t->in_use = TRUE;
-  g_hash_table_insert (self->textures, GINT_TO_POINTER (texture_id), t);
-  gsk_profiler_counter_inc (self->profiler, self->counters.created_textures);
-
-  return t;
-}
-
-static void
-gsk_gl_driver_release_texture (gpointer data)
-{
-  Texture *t = data;
-
-  t->user = NULL;
-}
-
-int
-gsk_gl_driver_get_texture_for_texture (GskGLDriver *driver,
-                                       GdkTexture  *texture,
-                                       int          min_filter,
-                                       int          mag_filter)
-{
-  Texture *t;
-  cairo_surface_t *surface;
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
-  g_return_val_if_fail (GDK_IS_TEXTURE (texture), -1);
-
-  t = gdk_texture_get_render_data (texture, driver);
-
-  if (t)
-    {
-      if (t->min_filter == min_filter && t->mag_filter == mag_filter)
-        return t->texture_id;
-    }
-
-  t = create_texture (driver, gdk_texture_get_width (texture), gdk_texture_get_height (texture));
-
-  if (gdk_texture_set_render_data (texture, driver, t, gsk_gl_driver_release_texture))
-    t->user = texture;
-
-  surface = gdk_texture_download_surface (texture);
-  gsk_gl_driver_bind_source_texture (driver, t->texture_id);
-  gsk_gl_driver_init_texture_with_surface (driver,
-                                           t->texture_id,
-                                           surface,
-                                           min_filter,
-                                           mag_filter);
-  cairo_surface_destroy (surface);
-
-  return t->texture_id;
-}
-
-int
-gsk_gl_driver_create_permanent_texture (GskGLDriver *self,
-                                        float        width,
-                                        float        height)
-{
-  Texture *t;
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), -1);
-
-  t = create_texture (self, width, height);
-  t->permanent = TRUE;
-
-  return t->texture_id;
-}
-
-int
-gsk_gl_driver_create_texture (GskGLDriver *driver,
-                              float        width,
-                              float        height)
-{
-  Texture *t;
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
-
-  t = create_texture (driver, width, height);
-
-  return t->texture_id;
-}
-
-static Vao *
-find_vao (GHashTable    *vaos,
-          int            position_id,
-          int            uv_id,
-          int            n_quads,
-          GskQuadVertex *quads)
-{
-  GHashTableIter iter;
-  gpointer value_p = NULL;
-
-  g_hash_table_iter_init (&iter, vaos);
-  while (g_hash_table_iter_next (&iter, NULL, &value_p))
-    {
-      Vao *v = value_p;
-
-      if (v->position_id != position_id || v->uv_id != uv_id)
-        continue;
-
-      if (v->n_quads != n_quads)
-        continue;
-
-      if (memcmp (v->quads, quads, sizeof (GskQuadVertex) * n_quads) == 0)
-        return v;
-    }
-
-  return NULL;
-}
-
-void
-gsk_gl_driver_create_permanent_vao_for_quad (GskGLDriver        *driver,
-                                             int                  n_vertices,
-                                             const GskQuadVertex *quads,
-                                             int                 *out_vao_id,
-                                             int                 *out_vao_buffer_id)
-{
-  GLuint vao_id, buffer_id;
-
-  glGenVertexArrays (1, &vao_id);
-  glBindVertexArray (vao_id);
-
-  glGenBuffers (1, &buffer_id);
-  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
-  glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_vertices, quads, GL_STATIC_DRAW);
-
-  glBindBuffer (GL_ARRAY_BUFFER, 0);
-  glBindVertexArray (0);
-
-  *out_vao_id = buffer_id;
-  *out_vao_buffer_id = vao_id;
-}
-
-int
-gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
-                                   int            position_id,
-                                   int            uv_id,
-                                   int            n_vertices,
-                                   GskQuadVertex *quads)
-
-{
-  GLuint vao_id, buffer_id;
-  Vao *v;
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
-  g_return_val_if_fail (driver->in_frame, -1);
-
-  v = find_vao (driver->vaos, position_id, uv_id, n_vertices, quads);
-  if (v != NULL && !v->in_use)
-    {
-      GSK_NOTE (OPENGL, g_print ("Reusing VAO(%d)\n", v->vao_id));
-      v->in_use = TRUE;
-      return v->vao_id;
-    }
-
-  glGenVertexArrays (1, &vao_id);
-  glBindVertexArray (vao_id);
-
-  glGenBuffers (1, &buffer_id);
-  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
-  glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_vertices, quads, GL_STATIC_DRAW);
-
-  if (position_id != -1)
-    {
-      glEnableVertexAttribArray (position_id);
-      glVertexAttribPointer (position_id, 2, GL_FLOAT, GL_FALSE,
-                             sizeof (GskQuadVertex),
-                             (void *) G_STRUCT_OFFSET (GskQuadVertex, position));
-    }
-
-  if (uv_id != -1)
-    {
-      glEnableVertexAttribArray (uv_id);
-      glVertexAttribPointer (uv_id, 2, GL_FLOAT, GL_FALSE,
-                             sizeof (GskQuadVertex),
-                             (void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
-    }
-
-  glBindBuffer (GL_ARRAY_BUFFER, 0);
-  glBindVertexArray (0);
-
-  v = vao_new ();
-  v->vao_id = vao_id;
-  v->buffer_id =  buffer_id;
-  v->position_id = position_id;
-  v->uv_id = uv_id;
-  v->n_quads = n_vertices;
-  v->quads = g_memdup (quads, sizeof (GskQuadVertex) * n_vertices);
-  v->in_use = TRUE;
-  g_hash_table_insert (driver->vaos, GINT_TO_POINTER (vao_id), v);
-
-#ifdef G_ENABLE_DEBUG
-  if (GSK_DEBUG_CHECK (OPENGL))
-    {
-      int i;
-      g_print ("New VAO(%d) for quad[%d] : {\n", v->vao_id, n_vertices);
-      for (i = 0; i < n_vertices; i++)
-        {
-          g_print ("  { x:%.2f, y:%.2f } { u:%.2f, v:%.2f }\n",
-                  quads[i].position[0], quads[i].position[1],
-                  quads[i].uv[0], quads[i].uv[1]);
-        }
-      g_print ("}\n");
-    }
-#endif
-
-  return vao_id;
-}
-
-int
-gsk_gl_driver_create_render_target (GskGLDriver *driver,
-                                    int          texture_id,
-                                    gboolean     add_depth_buffer,
-                                    gboolean     add_stencil_buffer)
-{
-  GLuint fbo_id, depth_stencil_buffer_id;
-  Texture *t;
-  Fbo f;
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
-  g_return_val_if_fail (driver->in_frame, -1);
-
-  t = gsk_gl_driver_get_texture (driver, texture_id);
-  if (t == NULL)
-    return -1;
-
-  if (t->fbos == NULL)
-    {
-      t->fbos = g_array_new (FALSE, FALSE, sizeof (Fbo));
-      g_array_set_clear_func (t->fbos, fbo_clear);
-    }
-
-  glGenFramebuffers (1, &fbo_id);
-  glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
-  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, t->texture_id, 0);
-
-  if (add_depth_buffer || add_stencil_buffer)
-    glGenRenderbuffersEXT (1, &depth_stencil_buffer_id);
-  else
-    depth_stencil_buffer_id = 0;
-
-  glBindRenderbuffer (GL_RENDERBUFFER, depth_stencil_buffer_id);
-
-  if (add_depth_buffer || add_stencil_buffer)
-    {
-      if (add_stencil_buffer)
-        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, t->width, t->height);
-      else
-        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, t->width, t->height);
-
-      if (add_depth_buffer)
-        glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
-                                   GL_RENDERBUFFER, depth_stencil_buffer_id);
-
-      if (add_stencil_buffer)
-        glFramebufferRenderbufferEXT (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
-                                      GL_RENDERBUFFER, depth_stencil_buffer_id);
-    }
-
-  f.fbo_id = fbo_id;
-  f.depth_stencil_id = depth_stencil_buffer_id;
-
-  g_array_append_val (t->fbos, f);
-
-  g_assert_cmpint (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);
-  glBindFramebuffer (GL_FRAMEBUFFER, driver->default_fbo.fbo_id);
-
-  return fbo_id;
-}
-
-void
-gsk_gl_driver_bind_source_texture (GskGLDriver *driver,
-                                   int          texture_id)
-{
-  Texture *t;
-
-  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
-  g_return_if_fail (driver->in_frame);
-
-  t = gsk_gl_driver_get_texture (driver, texture_id);
-  if (t == NULL)
-    {
-      g_critical ("No texture %d found.", texture_id);
-      return;
-    }
-
-  if (driver->bound_source_texture != t)
-    {
-      glActiveTexture (GL_TEXTURE0);
-      glBindTexture (GL_TEXTURE_2D, t->texture_id);
-
-      driver->bound_source_texture = t;
-    }
-}
-
-void
-gsk_gl_driver_bind_mask_texture (GskGLDriver *driver,
-                                 int          texture_id)
-{
-  Texture *t;
-
-  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
-  g_return_if_fail (driver->in_frame);
-
-  t = gsk_gl_driver_get_texture (driver, texture_id);
-  if (t == NULL)
-    {
-      g_critical ("No texture %d found.", texture_id);
-      return;
-    }
-
-  if (driver->bound_mask_texture != t)
-    {
-      glActiveTexture (GL_TEXTURE0 + 1);
-      glBindTexture (GL_TEXTURE_2D, t->texture_id);
-
-      glActiveTexture (GL_TEXTURE0);
-
-      driver->bound_mask_texture = t;
-    }
-}
-
-void
-gsk_gl_driver_bind_vao (GskGLDriver *driver,
-                        int          vao_id)
-{
-  Vao *v;
-
-  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
-  g_return_if_fail (driver->in_frame);
-
-  v = gsk_gl_driver_get_vao (driver, vao_id);
-  if (v == NULL)
-    {
-      g_critical ("No VAO %d found.", vao_id);
-      return;
-    }
-
-  if (driver->bound_vao != v)
-    {
-      glBindVertexArray (v->vao_id);
-      glBindBuffer (GL_ARRAY_BUFFER, v->buffer_id);
-
-      if (v->position_id != -1)
-        glEnableVertexAttribArray (v->position_id);
-
-      if (v->uv_id != -1)
-        glEnableVertexAttribArray (v->uv_id);
-
-      driver->bound_vao = v;
-    }
-}
-
-gboolean
-gsk_gl_driver_bind_render_target (GskGLDriver *driver,
-                                  int          texture_id)
-{
-  int status;
-  Fbo *f;
-
-  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), FALSE);
-  g_return_val_if_fail (driver->in_frame, FALSE);
-
-  if (texture_id == 0)
-    {
-      glBindFramebuffer (GL_FRAMEBUFFER, 0);
-      driver->bound_fbo = &driver->default_fbo;
-      goto out;
-    }
-
-  f = gsk_gl_driver_get_fbo (driver, texture_id);
-  if (f == NULL)
-    {
-      g_critical ("No render target associated to texture %d found.", texture_id);
-      return FALSE;
-    }
-
-  if (f != driver->bound_fbo)
-    {
-      glBindFramebuffer (GL_FRAMEBUFFER, f->fbo_id);
-
-      driver->bound_fbo = f;
-    }
-
-out:
-
-  if (texture_id != 0)
-    {
-      status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
-      g_assert_cmpint (status, ==, GL_FRAMEBUFFER_COMPLETE);
-    }
-
-  return TRUE;
-}
-
-void
-gsk_gl_driver_destroy_texture (GskGLDriver *driver,
-                               int          texture_id)
-{
-  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
-
-  g_hash_table_remove (driver->textures, GINT_TO_POINTER (texture_id));
-}
-
-void
-gsk_gl_driver_destroy_vao (GskGLDriver *driver,
-                           int          vao_id)
-{
-  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
-
-  g_hash_table_remove (driver->vaos, GINT_TO_POINTER (vao_id));
-}
-
-static void
-gsk_gl_driver_set_texture_parameters (GskGLDriver *driver,
-                                      int          min_filter,
-                                      int          mag_filter)
-{
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
-
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-}
-
-void
-gsk_gl_driver_init_texture_empty (GskGLDriver *driver,
-                                  int          texture_id)
-{
-  Texture *t;
-
-  g_return_if_fail (GSK_IS_GL_DRIVER (driver));
-
-  t = gsk_gl_driver_get_texture (driver, texture_id);
-  if (t == NULL)
-    {
-      g_critical ("No texture %d found.", texture_id);
-      return;
-    }
-
-  if (!(driver->bound_source_texture == t || driver->bound_mask_texture == t))
-    {
-      g_critical ("You must bind the texture before initializing it.");
-      return;
-    }
-
-  gsk_gl_driver_set_texture_parameters (driver, t->min_filter, t->mag_filter);
-
-  if (gdk_gl_context_get_use_es (driver->gl_context))
-    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-  else
-    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
-
-  glBindTexture (GL_TEXTURE_2D, 0);
-}
-
-void
-gsk_gl_driver_init_texture_with_surface (GskGLDriver     *self,
-                                         int              texture_id,
-                                         cairo_surface_t *surface,
-                                         int              min_filter,
-                                         int              mag_filter)
-{
-  Texture *t;
-
-  g_return_if_fail (GSK_IS_GL_DRIVER (self));
-
-  t = gsk_gl_driver_get_texture (self, texture_id);
-  if (t == NULL)
-    {
-      g_critical ("No texture %d found.", texture_id);
-      return;
-    }
-
-  if (!(self->bound_source_texture == t || self->bound_mask_texture == t))
-    {
-      g_critical ("You must bind the texture before initializing it.");
-      return;
-    }
-
-  gsk_gl_driver_set_texture_parameters (self, min_filter, mag_filter);
-
-  gdk_cairo_surface_upload_to_gl (surface, GL_TEXTURE_2D, t->width, t->height, NULL);
-  gsk_profiler_counter_inc (self->profiler, self->counters.surface_uploads);
-
-  t->min_filter = min_filter;
-  t->mag_filter = mag_filter;
-
-  if (t->min_filter != GL_NEAREST)
-    glGenerateMipmap (GL_TEXTURE_2D);
-}
diff --git a/gsk/gskgldriverprivate.h b/gsk/gskgldriverprivate.h
deleted file mode 100644 (file)
index f5f9986..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-#ifndef __GSK_GL_DRIVER_PRIVATE_H__
-#define __GSK_GL_DRIVER_PRIVATE_H__
-
-#include <cairo.h>
-#include <gdk/gdk.h>
-#include <graphene.h>
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_DRIVER (gsk_gl_driver_get_type ())
-
-G_DECLARE_FINAL_TYPE (GskGLDriver, gsk_gl_driver, GSK, GL_DRIVER, GObject)
-
-typedef struct {
-  float position[2];
-  float uv[2];
-} GskQuadVertex;
-
-GskGLDriver *   gsk_gl_driver_new                       (GdkGLContext    *context);
-
-int             gsk_gl_driver_get_max_texture_size      (GskGLDriver     *driver);
-
-void            gsk_gl_driver_begin_frame               (GskGLDriver     *driver);
-void            gsk_gl_driver_end_frame                 (GskGLDriver     *driver);
-
-int             gsk_gl_driver_get_texture_for_texture   (GskGLDriver     *driver,
-                                                         GdkTexture      *texture,
-                                                         int              min_filter,
-                                                         int              mag_filter);
-int             gsk_gl_driver_create_permanent_texture  (GskGLDriver     *driver,
-                                                         float            width,
-                                                         float            height);
-int             gsk_gl_driver_create_texture            (GskGLDriver     *driver,
-                                                         float            width,
-                                                         float            height);
-int             gsk_gl_driver_create_vao_for_quad       (GskGLDriver     *driver,
-                                                         int              position_id,
-                                                         int              uv_id,
-                                                         int              n_vertices,
-                                                         GskQuadVertex   *vertices);
-void           gsk_gl_driver_create_permanent_vao_for_quad (GskGLDriver        *driver,
-                                                            int                  n_vertices,
-                                                            const GskQuadVertex *quads,
-                                                            int                 *out_vao_id,
-                                                            int                 *out_vao_buffer_id);
-int             gsk_gl_driver_create_render_target      (GskGLDriver     *driver,
-                                                         int              texture_id,
-                                                         gboolean         add_depth_buffer,
-                                                         gboolean         add_stencil_buffer);
-
-void            gsk_gl_driver_bind_source_texture       (GskGLDriver     *driver,
-                                                         int              texture_id);
-void            gsk_gl_driver_bind_mask_texture         (GskGLDriver     *driver,
-                                                         int              texture_id);
-void            gsk_gl_driver_bind_vao                  (GskGLDriver     *driver,
-                                                         int              vao_id);
-gboolean        gsk_gl_driver_bind_render_target        (GskGLDriver     *driver,
-                                                         int              texture_id);
-
-void            gsk_gl_driver_init_texture_empty        (GskGLDriver     *driver,
-                                                         int              texture_id);
-void            gsk_gl_driver_init_texture_with_surface (GskGLDriver     *driver,
-                                                         int              texture_id,
-                                                         cairo_surface_t *surface,
-                                                         int              min_filter,
-                                                         int              mag_filter);
-
-void            gsk_gl_driver_destroy_texture           (GskGLDriver     *driver,
-                                                         int              texture_id);
-void            gsk_gl_driver_destroy_vao               (GskGLDriver     *driver,
-                                                         int              vao_id);
-
-int             gsk_gl_driver_collect_textures          (GskGLDriver     *driver);
-int             gsk_gl_driver_collect_vaos              (GskGLDriver     *driver);
-
-G_END_DECLS
-
-#endif /* __GSK_GL_DRIVER_PRIVATE_H__ */
diff --git a/gsk/gskglglyphcache.c b/gsk/gskglglyphcache.c
deleted file mode 100644 (file)
index 9e253e2..0000000
+++ /dev/null
@@ -1,445 +0,0 @@
-#include "config.h"
-
-#include "gskglglyphcacheprivate.h"
-#include "gskgldriverprivate.h"
-#include "gskdebugprivate.h"
-#include "gskprivate.h"
-
-#include <graphene.h>
-#include <cairo/cairo.h>
-#include <epoxy/gl.h>
-
-/* Parameters for our cache eviction strategy.
- *
- * Each cached glyph has an age that gets reset every time a cached glyph gets used.
- * Glyphs that have not been used for the MAX_AGE frames are considered old. We keep
- * count of the pixels of each atlas that are taken up by old glyphs. We check the
- * fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD, then
- * we drop the atlas an all the glyphs contained in it from the cache.
- */
-
-#define MAX_AGE 60
-#define CHECK_INTERVAL 10
-#define MAX_OLD 0.333
-
-#define ATLAS_SIZE 512
-
-typedef struct
-{
-  PangoFont *font;
-  PangoGlyph glyph;
-  guint scale; /* times 1024 */
-} GlyphCacheKey;
-
-typedef struct
-{
-  GlyphCacheKey *key;
-  GskGLCachedGlyph *value;
-  cairo_surface_t *surface;
-} DirtyGlyph;
-
-
-static guint    glyph_cache_hash       (gconstpointer v);
-static gboolean glyph_cache_equal      (gconstpointer v1,
-                                        gconstpointer v2);
-static void     glyph_cache_key_free   (gpointer      v);
-static void     glyph_cache_value_free (gpointer      v);
-static void     dirty_glyph_free       (gpointer      v);
-
-static GskGLGlyphAtlas *
-create_atlas (GskGLGlyphCache *cache)
-{
-  GskGLGlyphAtlas *atlas;
-
-  atlas = g_new0 (GskGLGlyphAtlas, 1);
-  atlas->width = ATLAS_SIZE;
-  atlas->height = ATLAS_SIZE;
-  atlas->y0 = 1;
-  atlas->y = 1;
-  atlas->x = 1;
-  atlas->image = NULL;
-  atlas->num_glyphs = 0;
-  atlas->dirty_glyphs = NULL;
-
-  return atlas;
-}
-
-static void
-free_atlas (gpointer v)
-{
-  GskGLGlyphAtlas *atlas = v;
-
-  if (atlas->image)
-    {
-      g_assert (atlas->image->texture_id == 0);
-      g_free (atlas->image);
-    }
-  g_list_free_full (atlas->dirty_glyphs, dirty_glyph_free);
-  g_free (atlas);
-}
-
-void
-gsk_gl_glyph_cache_init (GskGLGlyphCache *self,
-                         GskGLDriver     *gl_driver)
-{
-  self->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
-                                            glyph_cache_key_free, glyph_cache_value_free);
-  self->atlases = g_ptr_array_new_with_free_func (free_atlas);
-  g_ptr_array_add (self->atlases, create_atlas (self));
-
-  self->gl_driver = gl_driver;
-}
-
-void
-gsk_gl_glyph_cache_free (GskGLGlyphCache *self)
-{
-  guint i;
-
-  for (i = 0; i < self->atlases->len; i ++)
-    {
-      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->image)
-        {
-          gsk_gl_image_destroy (atlas->image, self->gl_driver);
-          atlas->image->texture_id = 0;
-        }
-    }
-
-  g_ptr_array_unref (self->atlases);
-  g_hash_table_unref (self->hash_table);
-}
-
-static gboolean
-glyph_cache_equal (gconstpointer v1, gconstpointer v2)
-{
-  const GlyphCacheKey *key1 = v1;
-  const GlyphCacheKey *key2 = v2;
-
-  return key1->font == key2->font &&
-         key1->glyph == key2->glyph &&
-         key1->scale == key2->scale;
-}
-
-static guint
-glyph_cache_hash (gconstpointer v)
-{
-  const GlyphCacheKey *key = v;
-
-  return GPOINTER_TO_UINT (key->font) ^ key->glyph ^ key->scale;
-}
-
-static void
-glyph_cache_key_free (gpointer v)
-{
-  GlyphCacheKey *f = v;
-
-  g_object_unref (f->font);
-  g_free (f);
-}
-
-static void
-glyph_cache_value_free (gpointer v)
-{
-  g_free (v);
-}
-
-static void
-dirty_glyph_free (gpointer v)
-{
-  DirtyGlyph *glyph = v;
-
-  if (glyph->surface)
-    cairo_surface_destroy (glyph->surface);
-  g_free (glyph);
-}
-
-static void
-add_to_cache (GskGLGlyphCache  *cache,
-              GlyphCacheKey        *key,
-              GskGLCachedGlyph *value)
-{
-  GskGLGlyphAtlas *atlas;
-  int i;
-  DirtyGlyph *dirty;
-  int width = value->draw_width * key->scale / 1024;
-  int height = value->draw_height * key->scale / 1024;
-
-  for (i = 0; i < cache->atlases->len; i++)
-    {
-      int x, y, y0;
-
-      atlas = g_ptr_array_index (cache->atlases, i);
-      x = atlas->x;
-      y = atlas->y;
-      y0 = atlas->y0;
-
-      if (atlas->x + width + 1 >= atlas->width)
-        {
-          /* start a new row */
-          y0 = y + 1;
-          x = 1;
-        }
-
-      if (y0 + height + 1 >= atlas->height)
-        continue;
-
-      atlas->y0 = y0;
-      atlas->x = x;
-      atlas->y = y;
-      break;
-    }
-
-  if (i == cache->atlases->len)
-    {
-      atlas = create_atlas (cache);
-      g_ptr_array_add (cache->atlases, atlas);
-    }
-
-  value->tx = (float)atlas->x / atlas->width;
-  value->ty = (float)atlas->y0 / atlas->height;
-  value->tw = (float)width / atlas->width;
-  value->th = (float)height / atlas->height;
-
-  value->atlas = atlas;
-
-  dirty = g_new0 (DirtyGlyph, 1);
-  dirty->key = key;
-  dirty->value = value;
-  atlas->dirty_glyphs = g_list_prepend (atlas->dirty_glyphs, dirty);
-
-  atlas->x = atlas->x + width + 1;
-  atlas->y = MAX (atlas->y, atlas->y0 + height + 1);
-
-  atlas->num_glyphs++;
-
-#ifdef G_ENABLE_DEBUG
-  if (GSK_DEBUG_CHECK(GLYPH_CACHE))
-    {
-      g_print ("Glyph cache:\n");
-      for (i = 0; i < cache->atlases->len; i++)
-        {
-          atlas = g_ptr_array_index (cache->atlases, i);
-          g_print ("\tGskGLGlyphAtlas %d (%dx%d): %d glyphs (%d dirty), %.2g%% old pixels, filled to %d, %d / %d\n",
-                   i, atlas->width, atlas->height,
-                   atlas->num_glyphs, g_list_length (atlas->dirty_glyphs),
-                   100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height),
-                   atlas->x, atlas->y0, atlas->y);
-        }
-    }
-#endif
-}
-
-static void
-render_glyph (const GskGLGlyphAtlas *atlas,
-              DirtyGlyph            *glyph,
-              GskImageRegion        *region)
-{
-  GlyphCacheKey *key = glyph->key;
-  GskGLCachedGlyph *value = glyph->value;
-  cairo_surface_t *surface;
-  cairo_t *cr;
-  cairo_scaled_font_t *scaled_font;
-  cairo_glyph_t cg;
-
-  scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
-  if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
-    return;
-
-  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                        value->draw_width * key->scale / 1024,
-                                        value->draw_height * key->scale / 1024);
-  cairo_surface_set_device_scale (surface, key->scale / 1024.0, key->scale / 1024.0);
-
-  cr = cairo_create (surface);
-
-  cairo_set_scaled_font (cr, scaled_font);
-  cairo_set_source_rgba (cr, 1, 1, 1, 1);
-
-  cg.index = key->glyph;
-  cg.x = - value->draw_x;
-  cg.y = - value->draw_y;
-
-  cairo_show_glyphs (cr, &cg, 1);
-
-  cairo_destroy (cr);
-
-  glyph->surface = surface;
-
-  region->data = cairo_image_surface_get_data (surface);
-  region->width = cairo_image_surface_get_width (surface);
-  region->height = cairo_image_surface_get_height (surface);
-  region->stride = cairo_image_surface_get_stride (surface);
-  region->x = (gsize)(value->tx * atlas->width);
-  region->y = (gsize)(value->ty * atlas->height);
-}
-
-static void
-upload_dirty_glyphs (GskGLGlyphCache *self,
-                     GskGLGlyphAtlas *atlas)
-{
-  GList *l;
-  guint num_regions;
-  GskImageRegion *regions;
-  int i;
-
-  num_regions = g_list_length (atlas->dirty_glyphs);
-  regions = alloca (sizeof (GskImageRegion) * num_regions);
-
-  for (l = atlas->dirty_glyphs, i = 0; l; l = l->next, i++)
-    render_glyph (atlas, (DirtyGlyph *)l->data, &regions[i]);
-
-  GSK_NOTE (GLYPH_CACHE,
-            g_print ("uploading %d glyphs to cache\n", num_regions));
-
-
-  gsk_gl_image_upload_regions (atlas->image, self->gl_driver, num_regions, regions);
-
-  g_list_free_full (atlas->dirty_glyphs, dirty_glyph_free);
-  atlas->dirty_glyphs = NULL;
-}
-
-const GskGLCachedGlyph *
-gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
-                           gboolean         create,
-                           PangoFont       *font,
-                           PangoGlyph       glyph,
-                           float            scale)
-{
-  GlyphCacheKey lookup_key;
-  GskGLCachedGlyph *value;
-
-  lookup_key.font = font;
-  lookup_key.glyph = glyph;
-  lookup_key.scale = (guint)(scale * 1024);
-
-  value = g_hash_table_lookup (cache->hash_table, &lookup_key);
-
-  if (value)
-    {
-      if (cache->timestamp - value->timestamp >= MAX_AGE)
-        {
-          GskGLGlyphAtlas *atlas = value->atlas;
-
-          if (atlas)
-            atlas->old_pixels -= value->draw_width * value->draw_height;
-
-          value->timestamp = cache->timestamp;
-        }
-    }
-
-  if (create && value == NULL)
-    {
-      GlyphCacheKey *key;
-      PangoRectangle ink_rect;
-
-      key = g_new0 (GlyphCacheKey, 1);
-      value = g_new0 (GskGLCachedGlyph, 1);
-
-      pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
-      pango_extents_to_pixels (&ink_rect, NULL);
-
-      value->draw_x = ink_rect.x;
-      value->draw_y = ink_rect.y;
-      value->draw_width = ink_rect.width;
-      value->draw_height = ink_rect.height;
-      value->timestamp = cache->timestamp;
-      value->atlas = NULL; /* For now */
-
-      key->font = g_object_ref (font);
-      key->glyph = glyph;
-      key->scale = (guint)(scale * 1024);
-
-      if (ink_rect.width > 0 && ink_rect.height > 0)
-        add_to_cache (cache, key, value);
-
-      g_hash_table_insert (cache->hash_table, key, value);
-    }
-
-  return value;
-}
-
-GskGLImage *
-gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache *self,
-                                    const GskGLCachedGlyph *glyph)
-{
-  GskGLGlyphAtlas *atlas = glyph->atlas;
-
-  g_assert (atlas != NULL);
-
-
-  if (atlas->image == NULL)
-    {
-      atlas->image = g_new0 (GskGLImage, 1);
-      gsk_gl_image_create (atlas->image, self->gl_driver, atlas->width, atlas->height);
-    }
-
-  if (atlas->dirty_glyphs)
-    upload_dirty_glyphs (self, atlas);
-
-  return atlas->image;
-}
-
-void
-gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
-{
-  int i;
-  GHashTableIter iter;
-  GlyphCacheKey *key;
-  GskGLCachedGlyph *value;
-  guint dropped = 0;
-
-  self->timestamp++;
-
-
-  if (self->timestamp % CHECK_INTERVAL != 0)
-    return;
-
-  /* look for glyphs that have grown old since last time */
-  g_hash_table_iter_init (&iter, self->hash_table);
-  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
-    {
-      guint age;
-
-      age = self->timestamp - value->timestamp;
-      if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
-        {
-          GskGLGlyphAtlas *atlas = value->atlas;
-
-          if (atlas)
-            atlas->old_pixels += value->draw_width * value->draw_height;
-        }
-    }
-
-  /* look for atlases to drop, and create a mapping of updated texture indices */
-  for (i = self->atlases->len - 1; i >= 0; i--)
-    {
-      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->old_pixels > MAX_OLD * atlas->width * atlas->height)
-        {
-          GSK_NOTE(GLYPH_CACHE,
-                   g_print ("Dropping atlas %d (%g.2%% old)\n",
-                            i, 100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height)));
-
-          if (atlas->image)
-            {
-              gsk_gl_image_destroy (atlas->image, self->gl_driver);
-              atlas->image->texture_id = 0;
-            }
-
-          /* Remove all glyphs that point to this atlas */
-          g_hash_table_iter_init (&iter, self->hash_table);
-          while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
-            {
-              if (value->atlas == atlas)
-                g_hash_table_iter_remove (&iter);
-            }
-          /* TODO: The above loop inside this other loop could be slow... */
-
-          g_ptr_array_remove_index (self->atlases, i);
-        }
-    }
-
-  GSK_NOTE(GLYPH_CACHE, g_print ("Dropped %d glyphs\n", dropped));
-}
diff --git a/gsk/gskglglyphcacheprivate.h b/gsk/gskglglyphcacheprivate.h
deleted file mode 100644 (file)
index 7166402..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-#ifndef __GSK_GL_GLYPH_CACHE_PRIVATE_H__
-#define __GSK_GL_GLYPH_CACHE_PRIVATE_H__
-
-#include "gskgldriverprivate.h"
-#include "gskglimageprivate.h"
-#include <pango/pango.h>
-#include <gdk/gdk.h>
-
-typedef struct
-{
-  GskGLDriver *gl_driver;
-
-  GHashTable *hash_table;
-  GPtrArray *atlases;
-
-  guint64 timestamp;
-} GskGLGlyphCache;
-
-
-typedef struct
-{
-  GskGLImage *image;
-  int width, height;
-  int x, y, y0;
-  int num_glyphs;
-  GList *dirty_glyphs;
-  guint old_pixels;
-} GskGLGlyphAtlas;
-
-typedef struct
-{
-  GskGLGlyphAtlas *atlas;
-
-  float tx;
-  float ty;
-  float tw;
-  float th;
-
-  int draw_x;
-  int draw_y;
-  int draw_width;
-  int draw_height;
-
-  guint64 timestamp;
-} GskGLCachedGlyph;
-
-void                     gsk_gl_glyph_cache_init            (GskGLGlyphCache        *self,
-                                                             GskGLDriver            *gl_driver);
-void                     gsk_gl_glyph_cache_free            (GskGLGlyphCache        *self);
-void                     gsk_gl_glyph_cache_begin_frame     (GskGLGlyphCache        *self);
-GskGLImage *             gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache        *self,
-                                                             const GskGLCachedGlyph *glyph);
-const GskGLCachedGlyph * gsk_gl_glyph_cache_lookup          (GskGLGlyphCache        *self,
-                                                             gboolean                create,
-                                                             PangoFont              *font,
-                                                             PangoGlyph              glyph,
-                                                             float                   scale);
-
-#endif
diff --git a/gsk/gskglimage.c b/gsk/gskglimage.c
deleted file mode 100644 (file)
index 6d91b35..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-
-#include "gskglimageprivate.h"
-#include <epoxy/gl.h>
-
-void
-gsk_gl_image_create (GskGLImage  *self,
-                     GskGLDriver *gl_driver,
-                     int          width,
-                     int          height)
-{
-  self->texture_id = gsk_gl_driver_create_permanent_texture (gl_driver, width, height);
-  self->width = width;
-  self->height = height;
-
-  gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);
-  gsk_gl_driver_init_texture_empty (gl_driver, self->texture_id);
-}
-
-void
-gsk_gl_image_destroy (GskGLImage  *self,
-                      GskGLDriver *gl_driver)
-{
-  gsk_gl_driver_destroy_texture (gl_driver, self->texture_id);
-}
-
-void
-gsk_gl_image_write_to_png (const GskGLImage *self,
-                           GskGLDriver      *gl_driver,
-                           const char       *filename)
-{
-  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, self->width);
-  guchar *data = g_malloc (self->height * stride);
-  cairo_surface_t *s;
-
-  gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);
-  glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
-  s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, self->width, self->height, stride);
-  cairo_surface_write_to_png (s, filename);
-
-  cairo_surface_destroy (s);
-  g_free (data);
-}
-
-void
-gsk_gl_image_upload_regions (GskGLImage           *self,
-                             GskGLDriver          *gl_driver,
-                             guint                 n_regions,
-                             const GskImageRegion *regions)
-{
-  guint i;
-
-  for (i = 0; i < n_regions; i ++)
-    {
-      const GskImageRegion *region = &regions[i];
-
-      gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);
-      glBindTexture (GL_TEXTURE_2D, self->texture_id);
-
-      glTexSubImage2D (GL_TEXTURE_2D, 0, region->x, region->y, region->width, region->height,
-                       GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, region->data);
-    }
-
-#ifdef G_ENABLE_DEBUG
-  /*gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);*/
-  /*gsk_gl_image_dump (self, gl_driver, "/home/baedert/atlases/test_dump.png");*/
-#endif
-}
-
diff --git a/gsk/gskglimageprivate.h b/gsk/gskglimageprivate.h
deleted file mode 100644 (file)
index 5a58fea..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef __GSK_GL_IMAGE_H__
-#define __GSK_GL_IMAGE_H__
-
-#include "gskgldriverprivate.h"
-#include <cairo/cairo.h>
-
-typedef struct
-{
-  guint texture_id;
-  int width;
-  int height;
-} GskGLImage;
-
-typedef struct
-{
-  guchar *data;
-  gsize width;
-  gsize height;
-  gsize stride;
-  gsize x;
-  gsize y;
-} GskImageRegion;
-
-void gsk_gl_image_create         (GskGLImage           *self,
-                                  GskGLDriver          *gl_driver,
-                                  int                   width,
-                                  int                   height);
-void gsk_gl_image_destroy        (GskGLImage           *self,
-                                  GskGLDriver          *gl_driver);
-void gsk_gl_image_write_to_png   (const GskGLImage     *self,
-                                  GskGLDriver          *gl_driver,
-                                  const char           *filename);
-void gsk_gl_image_upload_regions (GskGLImage           *self,
-                                  GskGLDriver          *gl_driver,
-                                  guint                 n_regions,
-                                  const GskImageRegion *regions);
-
-
-#endif
-
diff --git a/gsk/gskglprofiler.c b/gsk/gskglprofiler.c
deleted file mode 100644 (file)
index 0d58ee8..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-#include "config.h"
-
-#include "gskglprofilerprivate.h"
-
-#include <epoxy/gl.h>
-
-#define N_QUERIES       4
-
-struct _GskGLProfiler
-{
-  GObject parent_instance;
-
-  GdkGLContext *gl_context;
-
-  /* Creating GL queries is kind of expensive, so we pay the
-   * price upfront and create a circular buffer of queries
-   */
-  GLuint gl_queries[N_QUERIES];
-  GLuint active_query;
-
-  gboolean has_timer : 1;
-  gboolean first_frame : 1;
-};
-
-enum {
-  PROP_GL_CONTEXT = 1,
-
-  N_PROPERTIES
-};
-
-static GParamSpec *gsk_gl_profiler_properties[N_PROPERTIES];
-
-G_DEFINE_TYPE (GskGLProfiler, gsk_gl_profiler, G_TYPE_OBJECT)
-
-static void
-gsk_gl_profiler_finalize (GObject *gobject)
-{
-  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
-
-  glDeleteQueries (N_QUERIES, self->gl_queries);
-
-  g_clear_object (&self->gl_context);
-
-  G_OBJECT_CLASS (gsk_gl_profiler_parent_class)->finalize (gobject);
-}
-
-static void
-gsk_gl_profiler_set_property (GObject      *gobject,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
-{
-  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
-
-  switch (prop_id)
-    {
-    case PROP_GL_CONTEXT:
-      self->gl_context = g_value_dup_object (value);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
-    }
-}
-
-static void
-gsk_gl_profiler_get_property (GObject    *gobject,
-                              guint       prop_id,
-                              GValue     *value,
-                              GParamSpec *pspec)
-{
-  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
-
-  switch (prop_id)
-    {
-    case PROP_GL_CONTEXT:
-      g_value_set_object (value, self->gl_context);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
-    }
-}
-
-static void
-gsk_gl_profiler_class_init (GskGLProfilerClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-  gobject_class->set_property = gsk_gl_profiler_set_property;
-  gobject_class->get_property = gsk_gl_profiler_get_property;
-  gobject_class->finalize = gsk_gl_profiler_finalize;
-
-  gsk_gl_profiler_properties[PROP_GL_CONTEXT] =
-    g_param_spec_object ("gl-context",
-                         "GL Context",
-                         "The GdkGLContext used by the GL profiler",
-                         GDK_TYPE_GL_CONTEXT,
-                         G_PARAM_READWRITE |
-                         G_PARAM_CONSTRUCT_ONLY |
-                         G_PARAM_STATIC_STRINGS);
-
-  g_object_class_install_properties (gobject_class, N_PROPERTIES, gsk_gl_profiler_properties);
-}
-
-static void
-gsk_gl_profiler_init (GskGLProfiler *self)
-{
-  glGenQueries (N_QUERIES, self->gl_queries);
-
-  self->first_frame = TRUE;
-  self->has_timer = epoxy_has_gl_extension ("GL_ARB_timer_query");
-}
-
-GskGLProfiler *
-gsk_gl_profiler_new (GdkGLContext *context)
-{
-  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
-
-  return g_object_new (GSK_TYPE_GL_PROFILER, "gl-context", context, NULL);
-}
-
-void
-gsk_gl_profiler_begin_gpu_region (GskGLProfiler *profiler)
-{
-  GLuint query_id;
-
-  g_return_if_fail (GSK_IS_GL_PROFILER (profiler));
-
-  if (!profiler->has_timer)
-    return;
-
-  query_id = profiler->gl_queries[profiler->active_query];
-  glBeginQuery (GL_TIME_ELAPSED, query_id);
-}
-
-guint64
-gsk_gl_profiler_end_gpu_region (GskGLProfiler *profiler)
-{
-  GLuint last_query_id;
-  GLint res;
-  GLuint64 elapsed;
-
-  g_return_val_if_fail (GSK_IS_GL_PROFILER (profiler), 0);
-
-  if (!profiler->has_timer)
-    return 0;
-
-  glEndQuery (GL_TIME_ELAPSED);
-
-  if (profiler->active_query == 0)
-    last_query_id = N_QUERIES - 1;
-  else
-    last_query_id = profiler->active_query - 1;
-
-  /* Advance iterator */
-  profiler->active_query += 1;
-  if (profiler->active_query == N_QUERIES)
-    profiler->active_query = 0;
-
-  /* If this is the first frame we already have a result */
-  if (profiler->first_frame)
-    {
-      profiler->first_frame = FALSE;
-      return 0;
-    }
-
-  glGetQueryObjectiv (profiler->gl_queries[last_query_id], GL_QUERY_RESULT_AVAILABLE, &res);
-  if (res == 1)
-    glGetQueryObjectui64v (profiler->gl_queries[last_query_id], GL_QUERY_RESULT, &elapsed);
-  else
-    elapsed = 0;
-
-  return elapsed;
-}
diff --git a/gsk/gskglprofilerprivate.h b/gsk/gskglprofilerprivate.h
deleted file mode 100644 (file)
index 5b2a24b..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef __GSK_GL_PROFILER_PRIVATE_H__
-#define __GSK_GL_PROFILER_PRIVATE_H__
-
-#include <gsk/gsktypes.h>
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_PROFILER (gsk_gl_profiler_get_type ())
-G_DECLARE_FINAL_TYPE (GskGLProfiler, gsk_gl_profiler, GSK, GL_PROFILER, GObject)
-
-GskGLProfiler * gsk_gl_profiler_new                     (GdkGLContext  *context);
-
-void            gsk_gl_profiler_begin_gpu_region        (GskGLProfiler *profiler);
-guint64         gsk_gl_profiler_end_gpu_region          (GskGLProfiler *profiler);
-
-G_END_DECLS
-
-#endif /* __GSK_GL_PROFILER_PRIVATE_H__ */
diff --git a/gsk/gskglrenderer.c b/gsk/gskglrenderer.c
deleted file mode 100644 (file)
index 3398861..0000000
+++ /dev/null
@@ -1,1706 +0,0 @@
-#include "config.h"
-
-#include "gskglrendererprivate.h"
-
-#include "gskdebugprivate.h"
-#include "gskenums.h"
-#include "gskgldriverprivate.h"
-#include "gskglprofilerprivate.h"
-#include "gskprofilerprivate.h"
-#include "gskrendererprivate.h"
-#include "gskrendernodeprivate.h"
-#include "gskshaderbuilderprivate.h"
-#include "gskglglyphcacheprivate.h"
-#include "gdk/gdktextureprivate.h"
-
-#include "gskprivate.h"
-
-#include <epoxy/gl.h>
-#include <cairo-ft.h>
-
-#define SHADER_VERSION_GLES             100
-#define SHADER_VERSION_GL2_LEGACY       110
-#define SHADER_VERSION_GL3_LEGACY       130
-#define SHADER_VERSION_GL3              150
-
-#define ORTHO_NEAR_PLANE        -10000
-#define ORTHO_FAR_PLANE          10000
-
-#define N_VERTICES      6
-#define N_PROGRAMS      6
-
-#define HIGHLIGHT_FALLBACK 0
-#define DEBUG_OPS          0
-
-#if DEBUG_OPS
-#define OP_PRINT(format, ...) g_print(format, ## __VA_ARGS__)
-#else
-#define OP_PRINT(format, ...)
-#endif
-
-#define INIT_PROGRAM_UNIFORM_LOCATION(program_name, location_name, uniform_name) \
-              G_STMT_START{\
-                self->program_name.location_name = glGetUniformLocation(self->program_name.id, uniform_name);\
-                g_assert (self->program_name.location_name != 0); \
-              }G_STMT_END
-
-
-
-static void G_GNUC_UNUSED
-dump_framebuffer (const char *filename, int w, int h)
-{
-  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, w);
-  guchar *data = g_malloc (h * stride);
-  cairo_surface_t *s;
-
-  glReadPixels (0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, data);
-  s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24, w, h, stride);
-  cairo_surface_write_to_png (s, filename);
-
-  cairo_surface_destroy (s);
-  g_free (data);
-}
-
-static gboolean G_GNUC_UNUSED
-font_has_color_glyphs (const PangoFont *font)
-{
-  cairo_scaled_font_t *scaled_font;
-  gboolean has_color = FALSE;
-
-  scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font);
-  if (cairo_scaled_font_get_type (scaled_font) == CAIRO_FONT_TYPE_FT)
-    {
-      FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
-      has_color = (FT_HAS_COLOR (ft_face) != 0);
-      cairo_ft_scaled_font_unlock_face (scaled_font);
-    }
-
-  return has_color;
-}
-
-static void
-gsk_gl_renderer_setup_render_mode (GskGLRenderer *self);
-
-typedef struct
-{
-  int index;        /* Into the renderer's program array */
-  const char *name; /* For debugging */
-
-  int id;
-  /* Common locations (gl_common)*/
-  int source_location;
-  int mask_location;
-  int uv_location;
-  int position_location;
-  int alpha_location;
-  int blend_mode_location;
-  int viewport_location;
-  int projection_location;
-  int modelview_location;
-  int clip_location;
-  int clip_corner_widths_location;
-  int clip_corner_heights_location;
-
-  /* Program-specific locations */
-  union {
-    struct {
-      int color_location;
-    };
-    struct {
-      int color_matrix_location;
-      int color_offset_location;
-    };
-    struct {
-      int n_color_stops_location;
-      int color_stops_location;
-      int color_offsets_location;
-      int start_point_location;
-      int end_point_location;
-    };
-    struct {
-      int clip_bounds_location;
-      int corner_widths_location;
-      int corner_heights_location;
-    };
-  };
-} Program;
-
-enum {
-  MODE_BLIT = 1,
-  MODE_COLOR,
-  MODE_COLORING,
-  MODE_TEXTURE,
-  MODE_COLOR_MATRIX,
-  MODE_LINEAR_GRADIENT,
-  N_MODES
-};
-
-enum {
-  OP_NONE,
-  OP_CHANGE_OPACITY         =  1,
-  OP_CHANGE_COLOR           =  2,
-  OP_CHANGE_PROJECTION      =  3,
-  OP_CHANGE_MODELVIEW       =  4,
-  OP_CHANGE_PROGRAM         =  5,
-  OP_CHANGE_RENDER_TARGET   =  6,
-  OP_CHANGE_CLIP            =  7,
-  OP_CHANGE_VIEWPORT        =  8,
-  OP_CHANGE_SOURCE_TEXTURE  =  9,
-  OP_CHANGE_VAO             =  10,
-  OP_CHANGE_LINEAR_GRADIENT =  11,
-  OP_DRAW                   =  12,
-};
-
-typedef struct
-{
-  guint op;
-
-  union {
-    float opacity;
-    graphene_matrix_t modelview; // TODO: Make both matrix members just "matrix".
-    graphene_matrix_t projection;
-    const Program *program;
-    GdkRGBA color;
-    gsize vao_offset;
-    GskQuadVertex vertex_data[N_VERTICES]; // New Quad
-    int texture_id;
-    int render_target_id;
-    GskRoundedRect clip;
-    graphene_rect_t viewport;
-    struct {
-      int n_color_stops;
-      float color_offsets[8];
-      float color_stops[4 * 8];
-      graphene_point_t start_point;
-      graphene_point_t end_point;
-    } linear_gradient;
-  };
-} RenderOp;
-
-#ifdef G_ENABLE_DEBUG
-typedef struct
-{
-  GQuark frames;
-  GQuark draw_calls;
-} ProfileCounters;
-
-typedef struct
-{
-  GQuark cpu_time;
-  GQuark gpu_time;
-} ProfileTimers;
-#endif
-
-
-typedef enum
-{
-  RENDER_FULL,
-  RENDER_SCISSOR
-} RenderMode;
-
-struct _GskGLRenderer
-{
-  GskRenderer parent_instance;
-
-  int scale_factor;
-
-  graphene_rect_t viewport;
-
-  guint frame_buffer;
-  guint depth_stencil_buffer;
-  guint texture_id;
-
-
-  GdkGLContext *gl_context;
-  GskGLDriver *gl_driver;
-  GskGLProfiler *gl_profiler;
-
-  union {
-    Program programs[N_PROGRAMS];
-    struct {
-      Program blend_program;
-      Program blit_program;
-      Program color_program;
-      Program coloring_program;
-      Program color_matrix_program;
-      Program linear_gradient_program;
-    };
-  };
-
-  GArray *render_ops;
-
-  GskGLGlyphCache glyph_cache;
-  int full_vao_id;
-  int full_vao_buffer_id;
-
-#ifdef G_ENABLE_DEBUG
-  ProfileCounters profile_counters;
-  ProfileTimers profile_timers;
-#endif
-
-  RenderMode render_mode;
-
-  gboolean has_buffers : 1;
-};
-
-struct _GskGLRendererClass
-{
-  GskRendererClass parent_class;
-};
-
-G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
-
-typedef struct
-{
-  /* Per-Program State */
-  struct {
-    GskRoundedRect clip;
-    graphene_matrix_t modelview;
-    graphene_matrix_t projection;
-    int source_texture;
-    graphene_rect_t viewport;
-  } program_state[N_PROGRAMS];
-
-  /* Current global state */
-  const Program *current_program;
-  int current_render_target;
-  int current_vao;
-  int current_texture;
-  GskRoundedRect current_clip;
-  graphene_matrix_t current_modelview;
-  graphene_matrix_t current_projection;
-  graphene_rect_t current_viewport;
-  float current_opacity;
-
-  gsize buffer_size;
-
-  GskGLRenderer *renderer;
-} RenderOpBuilder;
-
-static void
-add_program_op (RenderOpBuilder *builder,
-                const Program   *new_program)
-{
-  static const GskRoundedRect empty_clip;
-  static const graphene_matrix_t empty_matrix;
-  static const graphene_rect_t empty_rect;
-  RenderOp op;
-
-  if (builder->current_program == new_program)
-    return;
-
-  op.op = OP_CHANGE_PROGRAM;
-  op.program = new_program;
-  g_array_append_val (builder->renderer->render_ops, op);
-  builder->current_program = new_program;
-
-  /* If the projection is not yet set for this program, we use the current one. */
-  if (memcmp (&empty_matrix, &builder->program_state[new_program->index].projection, sizeof (graphene_matrix_t)) == 0 ||
-      memcmp (&builder->current_projection, &builder->program_state[new_program->index].projection, sizeof (graphene_matrix_t)) != 0)
-    {
-      op.op = OP_CHANGE_PROJECTION;
-      op.projection = builder->current_projection;
-      g_array_append_val (builder->renderer->render_ops, op);
-      builder->program_state[new_program->index].projection = builder->current_projection;
-    }
-
-  if (memcmp (&empty_matrix, &builder->program_state[new_program->index].modelview, sizeof (graphene_matrix_t)) == 0 ||
-      memcmp (&builder->current_modelview, &builder->program_state[new_program->index].modelview, sizeof (graphene_matrix_t)) != 0)
-    {
-      op.op = OP_CHANGE_MODELVIEW;
-      op.modelview = builder->current_modelview;
-      g_array_append_val (builder->renderer->render_ops, op);
-      builder->program_state[new_program->index].modelview = builder->current_modelview;
-    }
-
-  if (memcmp (&empty_rect, &builder->program_state[new_program->index].viewport, sizeof (graphene_rect_t)) == 0 ||
-      memcmp (&builder->current_viewport, &builder->program_state[new_program->index].viewport, sizeof (graphene_rect_t)) != 0)
-    {
-      op.op = OP_CHANGE_VIEWPORT;
-      op.viewport = builder->current_viewport;
-      g_array_append_val (builder->renderer->render_ops, op);
-      builder->program_state[new_program->index].viewport = builder->current_viewport;
-    }
-
-  if (memcmp (&empty_clip, &builder->program_state[new_program->index].clip, sizeof (GskRoundedRect)) == 0 ||
-      memcmp (&builder->current_clip, &builder->program_state[new_program->index].clip, sizeof (GskRoundedRect)) != 0)
-    {
-      op.op = OP_CHANGE_CLIP;
-      op.clip = builder->current_clip;
-      g_array_append_val (builder->renderer->render_ops, op);
-      builder->program_state[new_program->index].clip = builder->current_clip;
-    }
-
-  if (graphene_rect_equal (&empty_rect, &builder->program_state[new_program->index].viewport) ||
-      !graphene_rect_equal (&builder->current_viewport, &builder->program_state[new_program->index].viewport))
-    {
-      op.op = OP_CHANGE_VIEWPORT;
-      op.viewport = builder->current_viewport;
-      g_array_append_val (builder->renderer->render_ops, op);
-      builder->program_state[new_program->index].viewport = builder->current_viewport;
-    }
-}
-
-static GskRoundedRect
-add_clip_op (RenderOpBuilder      *builder,
-             const GskRoundedRect *new_clip)
-{
-  RenderOp op;
-  GskRoundedRect prev_clip;
-
-  op.op = OP_CHANGE_CLIP;
-  op.clip = *new_clip;
-  g_array_append_val (builder->renderer->render_ops, op);
-
-  if (builder->current_program != NULL)
-    builder->program_state[builder->current_program->index].clip = *new_clip;
-
-  prev_clip = builder->current_clip;
-  builder->current_clip = *new_clip;
-
-  return prev_clip;
-}
-
-static graphene_matrix_t
-add_modelview_op (RenderOpBuilder         *builder,
-                  const graphene_matrix_t *matrix)
-{
-  RenderOp op;
-  graphene_matrix_t prev_mv;
-  RenderOp *last_op;
-
-  last_op = &g_array_index (builder->renderer->render_ops, RenderOp, builder->renderer->render_ops->len - 1);
-  if (last_op->op == OP_CHANGE_MODELVIEW)
-    {
-      last_op->modelview = *matrix;
-    }
-  else
-    {
-      op.op = OP_CHANGE_MODELVIEW;
-      op.modelview = *matrix;
-      g_array_append_val (builder->renderer->render_ops, op);
-    }
-
-  if (builder->current_program != NULL)
-    builder->program_state[builder->current_program->index].modelview = *matrix;
-
-  prev_mv = builder->current_modelview;
-  builder->current_modelview = *matrix;
-
-  return prev_mv;
-}
-
-static graphene_matrix_t
-add_projection_op (RenderOpBuilder         *builder,
-                   const graphene_matrix_t *matrix)
-{
-  RenderOp op;
-  graphene_matrix_t prev_proj;
-
-  op.op = OP_CHANGE_PROJECTION;
-  op.projection = *matrix;
-  g_array_append_val (builder->renderer->render_ops, op);
-
-  if (builder->current_program != NULL)
-    builder->program_state[builder->current_program->index].projection = *matrix;
-
-  prev_proj = builder->current_projection;
-  builder->current_projection = *matrix;
-
-  return prev_proj;
-}
-
-static graphene_rect_t
-add_viewport_op (RenderOpBuilder       *builder,
-                 const graphene_rect_t *viewport)
-{
-  RenderOp op;
-  graphene_rect_t prev_viewport;
-
-  op.op = OP_CHANGE_VIEWPORT;
-  op.viewport = *viewport;
-  g_array_append_val (builder->renderer->render_ops, op);
-
-  if (builder->current_program != NULL)
-    builder->program_state[builder->current_program->index].viewport = *viewport;
-
-  prev_viewport = builder->current_viewport;
-  builder->current_viewport = *viewport;
-
-  return prev_viewport;
-}
-
-static void
-add_texture_op (RenderOpBuilder *builder,
-                int              texture_id)
-{
-  RenderOp op;
-
-  if (builder->current_texture == texture_id)
-    return;
-
-  op.op = OP_CHANGE_SOURCE_TEXTURE;
-  op.texture_id = texture_id;
-  g_array_append_val (builder->renderer->render_ops, op);
-  builder->current_texture = texture_id;
-}
-
-static float
-add_opacity_op (RenderOpBuilder *builder,
-                float            opacity)
-{
-  RenderOp op;
-  float prev_opacity;
-
-  if (builder->current_opacity == opacity)
-    return opacity;
-
-  op.op = OP_CHANGE_OPACITY;
-  op.opacity = opacity;
-  g_array_append_val (builder->renderer->render_ops, op);
-
-  prev_opacity = builder->current_opacity;
-  builder->current_opacity = opacity;
-
-  return prev_opacity;
-}
-
-static int
-add_render_target_op (RenderOpBuilder *builder,
-                      int              render_target_id)
-{
-  RenderOp op;
-  int prev_render_target;
-
-  if (builder->current_render_target == render_target_id)
-    return render_target_id;
-
-  prev_render_target = builder->current_render_target;
-  op.op = OP_CHANGE_RENDER_TARGET;
-  op.render_target_id = render_target_id;
-  g_array_append_val (builder->renderer->render_ops, op);
-  builder->current_render_target = render_target_id;
-
-  return prev_render_target;
-}
-
-static void
-add_draw_op (RenderOpBuilder     *builder,
-             const GskQuadVertex  vertex_data[N_VERTICES])
-{
-  RenderOp op;
-  gsize offset = builder->buffer_size / sizeof (GskQuadVertex);
-
-  op.op = OP_CHANGE_VAO;
-  memcpy (&op.vertex_data, vertex_data, sizeof(GskQuadVertex) * N_VERTICES);
-  g_array_append_val (builder->renderer->render_ops, op);
-  builder->buffer_size += sizeof (GskQuadVertex) * N_VERTICES;
-
-  op.op = OP_DRAW;
-  op.vao_offset = offset;
-  g_array_append_val (builder->renderer->render_ops, op);
-}
-
-static void
-add_op (RenderOpBuilder *builder,
-        const RenderOp  *op)
-{
-  g_array_append_val (builder->renderer->render_ops, *op);
-}
-
-static void
-gsk_gl_renderer_dispose (GObject *gobject)
-{
-  GskGLRenderer *self = GSK_GL_RENDERER (gobject);
-
-  g_clear_pointer (&self->render_ops, g_array_unref);
-
-  G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
-}
-
-static void
-gsk_gl_renderer_create_buffers (GskGLRenderer *self,
-                                int            width,
-                                int            height,
-                                int            scale_factor)
-{
-  if (self->has_buffers)
-    return;
-
-  GSK_NOTE (OPENGL, g_print ("Creating buffers (w:%d, h:%d, scale:%d)\n", width, height, scale_factor));
-
-  if (self->texture_id == 0)
-    {
-      self->texture_id = gsk_gl_driver_create_texture (self->gl_driver,
-                                                       width * scale_factor,
-                                                       height * scale_factor);
-      gsk_gl_driver_bind_source_texture (self->gl_driver, self->texture_id);
-      gsk_gl_driver_init_texture_empty (self->gl_driver, self->texture_id);
-    }
-
-  gsk_gl_driver_create_render_target (self->gl_driver, self->texture_id, TRUE, TRUE);
-  gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id);
-
-  self->has_buffers = TRUE;
-}
-
-static void
-gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
-{
-  if (self->gl_context == NULL)
-    return;
-
-  if (!self->has_buffers)
-    return;
-
-  GSK_NOTE (OPENGL, g_print ("Destroying buffers\n"));
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  if (self->texture_id != 0)
-    {
-      gsk_gl_driver_destroy_texture (self->gl_driver, self->texture_id);
-      self->texture_id = 0;
-    }
-
-  self->has_buffers = FALSE;
-}
-
-static void
-init_common_locations (GskGLRenderer    *self,
-                       GskShaderBuilder *builder,
-                       Program          *prog)
-{
-  prog->source_location = glGetUniformLocation (prog->id, "uSource");
-  prog->mask_location = glGetUniformLocation (prog->id, "uMask");
-  prog->alpha_location = glGetUniformLocation (prog->id, "uAlpha");
-  prog->blend_mode_location = glGetUniformLocation (prog->id, "uBlendMode");
-  prog->viewport_location = glGetUniformLocation (prog->id, "uViewport");
-  prog->projection_location = glGetUniformLocation (prog->id, "uProjection");
-  prog->modelview_location = glGetUniformLocation (prog->id, "uModelview");
-  prog->clip_location = glGetUniformLocation (prog->id, "uClip");
-  prog->clip_corner_widths_location = glGetUniformLocation (prog->id, "uClipCornerWidths");
-  prog->clip_corner_heights_location = glGetUniformLocation (prog->id, "uClipCornerHeights");
-
-  prog->position_location = glGetAttribLocation (prog->id, "aPosition");
-  prog->uv_location = glGetAttribLocation (prog->id, "aUv");
-}
-
-static gboolean
-gsk_gl_renderer_create_programs (GskGLRenderer  *self,
-                                 GError        **error)
-{
-  GskShaderBuilder *builder;
-  GError *shader_error = NULL;
-  gboolean res = FALSE;
-
-  builder = gsk_shader_builder_new ();
-
-  gsk_shader_builder_set_resource_base_path (builder, "/org/gtk/libgsk/glsl");
-
-  if (gdk_gl_context_get_use_es (self->gl_context))
-    {
-      gsk_shader_builder_set_version (builder, SHADER_VERSION_GLES);
-      gsk_shader_builder_set_vertex_preamble (builder, "es2_common.vs.glsl");
-      gsk_shader_builder_set_fragment_preamble (builder, "es2_common.fs.glsl");
-      gsk_shader_builder_add_define (builder, "GSK_GLES", "1");
-    }
-  else if (gdk_gl_context_is_legacy (self->gl_context))
-    {
-      int maj, min;
-      gdk_gl_context_get_version (self->gl_context, &maj, &min);
-
-      if (maj == 3)
-        gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3_LEGACY);
-      else
-        gsk_shader_builder_set_version (builder, SHADER_VERSION_GL2_LEGACY);
-
-      gsk_shader_builder_set_vertex_preamble (builder, "gl_common.vs.glsl");
-      gsk_shader_builder_set_fragment_preamble (builder, "gl_common.fs.glsl");
-      gsk_shader_builder_add_define (builder, "GSK_LEGACY", "1");
-    }
-  else
-    {
-      gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3);
-      gsk_shader_builder_set_vertex_preamble (builder, "gl3_common.vs.glsl");
-      gsk_shader_builder_set_fragment_preamble (builder, "gl3_common.fs.glsl");
-      gsk_shader_builder_add_define (builder, "GSK_GL3", "1");
-    }
-
-#ifdef G_ENABLE_DEBUG
-  if (GSK_RENDER_MODE_CHECK (SHADERS))
-    gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1");
-#endif
-
-  self->blend_program.id =  gsk_shader_builder_create_program (builder,
-                                                               "blend.vs.glsl", "blend.fs.glsl",
-                                                               &shader_error);
-  if (shader_error != NULL)
-    {
-      g_propagate_prefixed_error (error,
-                                  shader_error,
-                                  "Unable to create 'blend' program: ");
-      goto out;
-    }
-  self->blend_program.index = 0;
-  init_common_locations (self, builder, &self->blend_program);
-
-  self->blit_program.id = gsk_shader_builder_create_program (builder,
-                                                             "blit.vs.glsl", "blit.fs.glsl",
-                                                             &shader_error);
-  if (shader_error != NULL)
-    {
-      g_propagate_prefixed_error (error,
-                                  shader_error,
-                                  "Unable to create 'blit' program: ");
-      goto out;
-    }
-  self->blit_program.index = 1;
-  init_common_locations (self, builder, &self->blit_program);
-
-  self->color_program.id = gsk_shader_builder_create_program (builder,
-                                                              "blit.vs.glsl", "color.fs.glsl",
-                                                              &shader_error);
-  if (shader_error != NULL)
-    {
-      g_propagate_prefixed_error (error,
-                                  shader_error,
-                                  "Unable to create 'color' program: ");
-      goto out;
-    }
-  self->color_program.index = 2;
-  init_common_locations (self, builder, &self->color_program);
-  INIT_PROGRAM_UNIFORM_LOCATION (color_program, color_location, "uColor");
-
-  self->coloring_program.id = gsk_shader_builder_create_program (builder,
-                                                                 "blit.vs.glsl", "coloring.fs.glsl",
-                                                                 &shader_error);
-  if (shader_error != NULL)
-    {
-      g_propagate_prefixed_error (error,
-                                  shader_error,
-                                  "Unable to create 'coloring' program: ");
-      goto out;
-    }
-  self->coloring_program.index = 3;
-  init_common_locations (self, builder, &self->coloring_program);
-  INIT_PROGRAM_UNIFORM_LOCATION (coloring_program, color_location, "uColor");
-
-  self->color_matrix_program.id = gsk_shader_builder_create_program (builder,
-                                                                     "blit.vs.glsl", "color_matrix.fs.glsl",
-                                                                     &shader_error);
-  if (shader_error != NULL)
-    {
-      g_propagate_prefixed_error (error,
-                                  shader_error,
-                                  "Unable to create 'color_matrix' program: ");
-      goto out;
-    }
-  self->color_matrix_program.index = 4;
-  init_common_locations (self, builder, &self->color_matrix_program);
-  INIT_PROGRAM_UNIFORM_LOCATION (color_matrix_program, color_matrix_location, "uColorMatrix");
-  INIT_PROGRAM_UNIFORM_LOCATION (color_matrix_program, color_offset_location, "uColorOffset");
-
-  self->linear_gradient_program.id = gsk_shader_builder_create_program (builder,
-                                                                        "blit.vs.glsl", "linear_gradient.fs.glsl",
-                                                                        &shader_error);
-  if (shader_error != NULL)
-    {
-      g_propagate_prefixed_error (error,
-                                  shader_error,
-                                  "Unable to create 'linear_gradient' program: ");
-      goto out;
-    }
-  self->linear_gradient_program.index = 5;
-  init_common_locations (self, builder, &self->linear_gradient_program);
-  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, color_stops_location, "uColorStops");
-  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, color_offsets_location, "uColorOffsets");
-  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, n_color_stops_location, "uNumColorStops");
-  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, start_point_location, "uStartPoint");
-  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, end_point_location, "uEndPoint");
-
-  res = TRUE;
-
-out:
-
-  g_object_unref (builder);
-  return res;
-}
-
-static gboolean
-gsk_gl_renderer_realize (GskRenderer  *renderer,
-                         GdkWindow    *window,
-                         GError      **error)
-{
-  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-  GskQuadVertex vertex_data[N_VERTICES] = {
-    { { 0, 0 }, { 0, 0 }, },
-    { { 0, 1 }, { 0, 1 }, },
-    { { 1, 0 }, { 1, 0 }, },
-
-    { { 1, 1 }, { 1, 1 }, },
-    { { 0, 1 }, { 0, 1 }, },
-    { { 1, 0 }, { 1, 0 }, },
-  };
-
-
-  self->scale_factor = gdk_window_get_scale_factor (window);
-
-  /* If we didn't get a GdkGLContext before realization, try creating
-   * one now, for our exclusive use.
-   */
-  if (self->gl_context == NULL)
-    {
-      self->gl_context = gdk_window_create_gl_context (window, error);
-      if (self->gl_context == NULL)
-        return FALSE;
-    }
-
-  if (!gdk_gl_context_realize (self->gl_context, error))
-    return FALSE;
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  g_assert (self->gl_driver == NULL);
-  self->gl_profiler = gsk_gl_profiler_new (self->gl_context);
-  self->gl_driver = gsk_gl_driver_new (self->gl_context);
-
-  GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n"));
-  if (!gsk_gl_renderer_create_programs (self, error))
-    return FALSE;
-
-  gsk_gl_glyph_cache_init (&self->glyph_cache, self->gl_driver);
-
-  gsk_gl_driver_create_permanent_vao_for_quad (self->gl_driver, N_VERTICES, vertex_data,
-                                               &self->full_vao_id, &self->full_vao_buffer_id);
-
-  return TRUE;
-}
-
-static void
-gsk_gl_renderer_unrealize (GskRenderer *renderer)
-{
-  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-
-  if (self->gl_context == NULL)
-    return;
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  /* We don't need to iterate to destroy the associated GL resources,
-   * as they will be dropped when we finalize the GskGLDriver
-   */
-  g_array_set_size (self->render_ops, 0);
-
-
-  glDeleteProgram (self->blend_program.id);
-  glDeleteProgram (self->blit_program.id);
-  glDeleteProgram (self->color_program.id);
-  glDeleteProgram (self->coloring_program.id);
-  glDeleteProgram (self->color_matrix_program.id);
-  glDeleteProgram (self->linear_gradient_program.id);
-
-  gsk_gl_renderer_destroy_buffers (self);
-
-  gsk_gl_glyph_cache_free (&self->glyph_cache);
-
-  g_clear_object (&self->gl_profiler);
-  g_clear_object (&self->gl_driver);
-
-  if (self->gl_context == gdk_gl_context_get_current ())
-    gdk_gl_context_clear_current ();
-
-  g_clear_object (&self->gl_context);
-}
-
-static GdkDrawingContext *
-gsk_gl_renderer_begin_draw_frame (GskRenderer          *renderer,
-                                  const cairo_region_t *update_area)
-{
-  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-  cairo_region_t *damage;
-  GdkDrawingContext *result;
-  GdkRectangle whole_window;
-  GdkWindow *window;
-
-  window = gsk_renderer_get_window (renderer);
-  whole_window = (GdkRectangle) {
-                     0, 0,
-                     gdk_window_get_width (window) * self->scale_factor,
-                     gdk_window_get_height (window) * self->scale_factor
-                 };
-  damage = gdk_gl_context_get_damage (self->gl_context);
-  cairo_region_union (damage, update_area);
-
-  if (cairo_region_contains_rectangle (damage, &whole_window) == CAIRO_REGION_OVERLAP_IN)
-    {
-      self->render_mode = RENDER_FULL;
-    }
-  else
-    {
-      GdkRectangle extents;
-
-      cairo_region_get_extents (damage, &extents);
-      cairo_region_union_rectangle (damage, &extents);
-
-      if (gdk_rectangle_equal (&extents, &whole_window))
-        self->render_mode = RENDER_FULL;
-      else
-        self->render_mode = RENDER_SCISSOR;
-    }
-
-  result = gdk_window_begin_draw_frame (window,
-                                        GDK_DRAW_CONTEXT (self->gl_context),
-                                        damage);
-
-  cairo_region_destroy (damage);
-
-  return result;
-}
-
-static void
-gsk_gl_renderer_resize_viewport (GskGLRenderer         *self,
-                                 const graphene_rect_t *viewport)
-{
-  int width = viewport->size.width;
-  int height = viewport->size.height;
-
-  GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %d, %d) [scale:%d]\n",
-                             width,
-                             height,
-                             self->scale_factor));
-
-  graphene_rect_init (&self->viewport, 0, 0, width, height);
-  glViewport (0, 0, width, height);
-}
-
-
-static void
-get_gl_scaling_filters (GskRenderNode *node,
-                        int           *min_filter_r,
-                        int           *mag_filter_r)
-{
-  *min_filter_r = GL_NEAREST;
-  *mag_filter_r = GL_NEAREST;
-}
-
-static void
-gsk_gl_renderer_clear_tree (GskGLRenderer *self)
-{
-  int removed_textures, removed_vaos;
-
-  if (self->gl_context == NULL)
-    return;
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  g_array_remove_range (self->render_ops, 0, self->render_ops->len);
-
-  removed_textures = gsk_gl_driver_collect_textures (self->gl_driver);
-  removed_vaos = gsk_gl_driver_collect_vaos (self->gl_driver);
-
-  GSK_NOTE (OPENGL, g_print ("Collected: %d textures, %d vaos\n",
-                             removed_textures,
-                             removed_vaos));
-}
-
-static void
-gsk_gl_renderer_clear (GskGLRenderer *self)
-{
-  GSK_NOTE (OPENGL, g_print ("Clearing viewport\n"));
-  glClearColor (0, 0, 0, 0);
-  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-}
-
-static void
-gsk_gl_renderer_setup_render_mode (GskGLRenderer *self)
-{
-  switch (self->render_mode)
-  {
-    case RENDER_FULL:
-      glDisable (GL_SCISSOR_TEST);
-      break;
-
-    case RENDER_SCISSOR:
-      {
-        GdkDrawingContext *context = gsk_renderer_get_drawing_context (GSK_RENDERER (self));
-        GdkWindow *window = gsk_renderer_get_window (GSK_RENDERER (self));
-        cairo_region_t *clip = gdk_drawing_context_get_clip (context);
-        cairo_rectangle_int_t extents;
-        int window_height;
-
-        /* Fall back to RENDER_FULL */
-        if (clip == NULL)
-          {
-            glDisable (GL_SCISSOR_TEST);
-            return;
-          }
-
-        g_assert (cairo_region_num_rectangles (clip) == 1);
-
-        window_height = gdk_window_get_height (window) * self->scale_factor;
-
-        /*cairo_region_get_extents (clip, &extents);*/
-        cairo_region_get_rectangle (clip, 0, &extents);
-
-        glEnable (GL_SCISSOR_TEST);
-        glScissor (extents.x * self->scale_factor,
-                   window_height - (extents.height * self->scale_factor) - (extents.y * self->scale_factor),
-                   extents.width * self->scale_factor,
-                   extents.height * self->scale_factor);
-
-        cairo_region_destroy (clip);
-        break;
-      }
-
-    default:
-      g_assert_not_reached ();
-      break;
-  }
-}
-
-
-static void
-gsk_gl_renderer_add_render_ops (GskGLRenderer   *self,
-                                GskRenderNode   *node,
-                                RenderOpBuilder *builder)
-{
-  float min_x = node->bounds.origin.x;
-  float min_y = node->bounds.origin.y;
-  float max_x = min_x + node->bounds.size.width;
-  float max_y = min_y + node->bounds.size.height;
-
-  /* Default vertex data */
-  GskQuadVertex vertex_data[N_VERTICES] = {
-    { { min_x, min_y }, { 0, 0 }, },
-    { { min_x, max_y }, { 0, 1 }, },
-    { { max_x, min_y }, { 1, 0 }, },
-
-    { { max_x, max_y }, { 1, 1 }, },
-    { { min_x, max_y }, { 0, 1 }, },
-    { { max_x, min_y }, { 1, 0 }, },
-  };
-
-  /*if (gsk_render_node_get_node_type (node) != GSK_CONTAINER_NODE)*/
-    /*g_message ("Adding ops for node %s with type %u", node->name,*/
-               /*gsk_render_node_get_node_type (node));*/
-
-
-  switch (gsk_render_node_get_node_type (node))
-    {
-    case GSK_NOT_A_RENDER_NODE:
-      g_assert_not_reached ();
-
-    case GSK_CONTAINER_NODE:
-      {
-        guint i, p;
-
-        for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i ++)
-          {
-            GskRenderNode *child = gsk_container_node_get_child (node, i);
-
-            gsk_gl_renderer_add_render_ops (self, child, builder);
-          }
-      }
-    break;
-
-    case GSK_COLOR_NODE:
-      {
-        RenderOp op;
-
-        add_program_op (builder, &self->color_program);
-        op.op = OP_CHANGE_COLOR;
-        op.color = *gsk_color_node_peek_color (node);
-        add_op (builder, &op);
-        add_draw_op (builder, vertex_data);
-      }
-    break;
-
-    case GSK_TEXTURE_NODE:
-      {
-        GdkTexture *texture = gsk_texture_node_get_texture (node);
-        int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
-        int texture_id;
-
-        get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
-
-        texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
-                                                            texture,
-                                                            gl_min_filter,
-                                                            gl_mag_filter);
-        add_program_op (builder, &self->blit_program);
-        add_texture_op (builder, texture_id);
-        add_draw_op (builder, vertex_data);
-      }
-    break;
-
-    case GSK_TRANSFORM_NODE:
-      {
-        GskRenderNode *child = gsk_transform_node_get_child (node);
-        graphene_matrix_t prev_mv;
-        graphene_matrix_t transform, transformed_mv;
-
-        graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node));
-        graphene_matrix_multiply (&transform, &builder->current_modelview, &transformed_mv);
-        prev_mv = add_modelview_op (builder, &transformed_mv);
-
-        gsk_gl_renderer_add_render_ops (self, child, builder);
-
-        add_modelview_op (builder, &prev_mv);
-      }
-    break;
-
-    case GSK_OPACITY_NODE:
-      {
-        int render_target;
-        int texture;
-        int prev_render_target;
-        float prev_opacity;
-        graphene_matrix_t identity;
-        graphene_matrix_t prev_projection;
-        graphene_matrix_t prev_modelview;
-        graphene_rect_t prev_viewport;
-        graphene_matrix_t item_proj;
-        GskQuadVertex vertex_data[N_VERTICES] = {
-          { { min_x, min_y }, { 0, 1 }, },
-          { { min_x, max_y }, { 0, 0 }, },
-          { { max_x, min_y }, { 1, 1 }, },
-
-          { { max_x, max_y }, { 1, 0 }, },
-          { { min_x, max_y }, { 0, 0 }, },
-          { { max_x, min_y }, { 1, 1 }, },
-        };
-
-        texture = gsk_gl_driver_create_texture (self->gl_driver,
-                                                node->bounds.size.width,
-                                                node->bounds.size.height);
-        gsk_gl_driver_bind_source_texture (self->gl_driver, texture);
-        gsk_gl_driver_init_texture_empty (self->gl_driver, texture);
-        render_target = gsk_gl_driver_create_render_target (self->gl_driver, texture, TRUE, TRUE);
-
-        graphene_matrix_init_ortho (&item_proj,
-                                    min_x, max_x,
-                                    min_y, max_y,
-                                    ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
-        graphene_matrix_scale (&item_proj, 1, -1, 1);
-        graphene_matrix_init_identity (&identity);
-
-        prev_render_target = add_render_target_op (builder, render_target);
-        prev_projection = add_projection_op (builder, &item_proj);
-        prev_modelview = add_modelview_op (builder, &identity);
-        prev_viewport = add_viewport_op (builder, &node->bounds);
-
-        gsk_gl_renderer_add_render_ops (self, gsk_opacity_node_get_child (node), builder);
-
-        add_viewport_op (builder, &prev_viewport);
-        add_modelview_op (builder, &prev_modelview);
-        add_projection_op (builder, &prev_projection);
-        add_render_target_op (builder, prev_render_target);
-
-        add_program_op (builder, &self->blit_program);
-        prev_opacity = add_opacity_op (builder, gsk_opacity_node_get_opacity (node));
-        add_texture_op (builder, texture);
-        add_draw_op (builder, vertex_data);
-        add_opacity_op (builder, prev_opacity);
-      }
-    break;
-
-    case GSK_LINEAR_GRADIENT_NODE:
-      {
-        RenderOp op;
-        int n_color_stops = MIN (8, gsk_linear_gradient_node_get_n_color_stops (node));
-        const GskColorStop *stops = gsk_linear_gradient_node_peek_color_stops (node);
-        const graphene_point_t *start = gsk_linear_gradient_node_peek_start (node);
-        const graphene_point_t *end = gsk_linear_gradient_node_peek_end (node);
-        int i;
-
-        for (i = 0; i < n_color_stops; i ++)
-          {
-            const GskColorStop *stop = stops + i;
-
-            op.linear_gradient.color_stops[(i * 4) + 0] = stop->color.red;
-            op.linear_gradient.color_stops[(i * 4) + 1] = stop->color.green;
-            op.linear_gradient.color_stops[(i * 4) + 2] = stop->color.blue;
-            op.linear_gradient.color_stops[(i * 4) + 3] = stop->color.alpha;
-            op.linear_gradient.color_offsets[i] = stop->offset;
-          }
-
-        add_program_op (builder, &self->linear_gradient_program);
-        op.op = OP_CHANGE_LINEAR_GRADIENT;
-        op.linear_gradient.n_color_stops = n_color_stops;
-        op.linear_gradient.start_point = *start;
-        op.linear_gradient.end_point = *end;
-        add_op (builder, &op);
-
-        add_draw_op (builder, vertex_data);
-      }
-    break;
-
-    case GSK_CLIP_NODE:
-      {
-        GskRoundedRect prev_clip;
-        GskRenderNode *child = gsk_clip_node_get_child (node);
-        graphene_rect_t transformed_clip;
-        graphene_rect_t intersection;
-        GskRoundedRect child_clip;
-
-        transformed_clip = *gsk_clip_node_peek_clip (node);
-        graphene_matrix_transform_bounds (&builder->current_modelview, &transformed_clip, &transformed_clip);
-
-        graphene_rect_intersection (&transformed_clip,
-                                    &builder->current_clip.bounds,
-                                    &intersection);
-
-        gsk_rounded_rect_init_from_rect (&child_clip, &intersection, 0.0f);
-
-        prev_clip = add_clip_op (builder, &child_clip);
-        gsk_gl_renderer_add_render_ops (self, child, builder);
-        add_clip_op (builder, &prev_clip);
-      }
-    break;
-
-    case GSK_ROUNDED_CLIP_NODE:
-      {
-        GskRoundedRect prev_clip;
-        GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
-        const GskRoundedRect *rounded_clip = gsk_rounded_clip_node_peek_clip (node);
-        graphene_rect_t transformed_clip;
-        graphene_rect_t intersection;
-        GskRoundedRect child_clip;
-
-        transformed_clip = rounded_clip->bounds;
-        graphene_matrix_transform_bounds (&builder->current_modelview, &transformed_clip, &transformed_clip);
-
-        graphene_rect_intersection (&transformed_clip, &builder->current_clip.bounds,
-                                    &intersection);
-        gsk_rounded_rect_init (&child_clip, &intersection,
-                               &rounded_clip->corner[0],
-                               &rounded_clip->corner[1],
-                               &rounded_clip->corner[2],
-                               &rounded_clip->corner[3]);
-
-        prev_clip = add_clip_op (builder, &child_clip);
-        gsk_gl_renderer_add_render_ops (self, child, builder);
-        add_clip_op (builder, &prev_clip);
-      }
-    break;
-
-    case GSK_TEXT_NODE:
-      {
-        const PangoFont *font = gsk_text_node_peek_font (node);
-        const PangoGlyphInfo *glyphs = gsk_text_node_peek_glyphs (node);
-        guint num_glyphs = gsk_text_node_get_num_glyphs (node);
-        int i;
-        int x_position = 0;
-        int x = gsk_text_node_get_x (node);
-        int y = gsk_text_node_get_y (node);
-
-        /* We use one quad per character, unlike the other nodes which
-         * use at most one quad altogether */
-        for (i = 0; i < num_glyphs; i++)
-          {
-            const PangoGlyphInfo *gi = &glyphs[i];
-            const GskGLCachedGlyph *glyph;
-            int glyph_x, glyph_y, glyph_w, glyph_h;
-            float tx, ty, tx2, ty2;
-            double cx;
-            double cy;
-
-            if (gi->glyph == PANGO_GLYPH_EMPTY ||
-                (gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG) > 0)
-              continue;
-
-            glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache,
-                                               TRUE,
-                                               (PangoFont *)font,
-                                               gi->glyph,
-                                               self->scale_factor);
-
-            /* e.g. whitespace */
-            if (glyph->draw_width <= 0 || glyph->draw_height <= 0)
-              {
-                x_position += gi->geometry.width;
-                continue;
-              }
-            cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
-            cy = (double)(gi->geometry.y_offset) / PANGO_SCALE;
-
-            /* If the font has color glyphs, we don't need to recolor anything */
-            if (font_has_color_glyphs (font))
-              {
-                add_program_op (builder, &self->blit_program);
-              }
-            else
-              {
-                RenderOp op;
-
-                add_program_op (builder, &self->coloring_program);
-
-                op.op = OP_CHANGE_COLOR;
-                op.color = *gsk_text_node_peek_color (node);
-                add_op (builder, &op);
-              }
-
-            add_texture_op (builder, gsk_gl_glyph_cache_get_glyph_image (&self->glyph_cache,
-                                                                         glyph)->texture_id);
-
-            {
-              tx  = glyph->tx;
-              ty  = glyph->ty;
-              tx2 = tx + glyph->tw;
-              ty2 = ty + glyph->th;
-
-              glyph_x = x + cx + glyph->draw_x;
-              glyph_y = y + cy + glyph->draw_y;
-              glyph_w = glyph->draw_width;
-              glyph_h = glyph->draw_height;
-
-              GskQuadVertex vertex_data[N_VERTICES] = {
-                { { glyph_x,           glyph_y           }, { tx,  ty  }, },
-                { { glyph_x,           glyph_y + glyph_h }, { tx,  ty2 }, },
-                { { glyph_x + glyph_w, glyph_y           }, { tx2, ty  }, },
-
-                { { glyph_x + glyph_w, glyph_y + glyph_h }, { tx2, ty2 }, },
-                { { glyph_x,           glyph_y + glyph_h }, { tx,  ty2 }, },
-                { { glyph_x + glyph_w, glyph_y           }, { tx2, ty  }, },
-              };
-
-              add_draw_op (builder, vertex_data);
-            }
-
-            x_position += gi->geometry.width;
-          }
-
-      }
-    break;
-
-
-    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
-    case GSK_BORDER_NODE:
-    case GSK_INSET_SHADOW_NODE:
-    case GSK_OUTSET_SHADOW_NODE:
-    case GSK_BLUR_NODE:
-    case GSK_SHADOW_NODE:
-    case GSK_CROSS_FADE_NODE:
-    case GSK_BLEND_NODE:
-    case GSK_REPEAT_NODE:
-    case GSK_CAIRO_NODE:
-    case GSK_COLOR_MATRIX_NODE:
-    default:
-      {
-        cairo_surface_t *surface;
-        cairo_t *cr;
-        int texture_id;
-
-        surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                              ceilf (node->bounds.size.width) * self->scale_factor,
-                                              ceilf (node->bounds.size.height) * self->scale_factor);
-        cairo_surface_set_device_scale (surface, self->scale_factor, self->scale_factor);
-        cr = cairo_create (surface);
-
-        cairo_save (cr);
-        cairo_translate (cr, -min_x, -min_y);
-        gsk_render_node_draw (node, cr);
-        cairo_restore (cr);
-
-#if HIGHLIGHT_FALLBACK
-        cairo_move_to (cr, 0, 0);
-        cairo_rectangle (cr, 0, 0, max_x - min_x, max_y - min_y);
-        cairo_set_source_rgba (cr, 1, 0, 0, 1);
-        cairo_stroke (cr);
-#endif
-        cairo_destroy (cr);
-
-        /* Upload the Cairo surface to a GL texture */
-        texture_id = gsk_gl_driver_create_texture (self->gl_driver,
-                                                   node->bounds.size.width * self->scale_factor,
-                                                   node->bounds.size.height * self->scale_factor);
-
-        gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id);
-        gsk_gl_driver_init_texture_with_surface (self->gl_driver,
-                                                 texture_id,
-                                                 surface,
-                                                 GL_NEAREST, GL_NEAREST);
-
-        cairo_surface_destroy (surface);
-
-        add_program_op (builder, &self->blit_program);
-        add_texture_op (builder, texture_id);
-        add_draw_op (builder, vertex_data);
-      }
-    }
-}
-
-static void
-gsk_gl_renderer_render_ops (GskGLRenderer *self,
-                            gsize          vertex_data_size)
-{
-  guint i;
-  guint n_ops = self->render_ops->len;
-  float mat[16];
-  const Program *program = NULL;
-  gsize buffer_index = 0;
-  float *vertex_data = g_malloc (vertex_data_size);
-
-  /*g_message ("%s: Buffer size: %ld", __FUNCTION__, vertex_data_size);*/
-
-
-  GLuint buffer_id, vao_id;
-  glGenVertexArrays (1, &vao_id);
-  glBindVertexArray (vao_id);
-
-  glGenBuffers (1, &buffer_id);
-  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
-
-
-  // Fill buffer data
-  for (i = 0; i < n_ops; i ++)
-    {
-      const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
-
-      if (op->op == OP_CHANGE_VAO)
-        {
-          memcpy (vertex_data + buffer_index, &op->vertex_data, sizeof (GskQuadVertex) * N_VERTICES);
-          buffer_index += sizeof (GskQuadVertex) * N_VERTICES / sizeof (float);
-        }
-    }
-
-  // Set buffer data
-  glBufferData (GL_ARRAY_BUFFER, vertex_data_size, vertex_data, GL_STATIC_DRAW);
-
-  // Describe buffer contents
-
-  /* 0 = position location */
-  glEnableVertexAttribArray (0);
-  glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
-                         sizeof (GskQuadVertex),
-                         (void *) G_STRUCT_OFFSET (GskQuadVertex, position));
-  /* 1 = texture coord location */
-  glEnableVertexAttribArray (1);
-  glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
-                         sizeof (GskQuadVertex),
-                         (void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
-
-  for (i = 0; i < n_ops; i ++)
-    {
-      const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
-
-      if (op->op == OP_NONE ||
-          op->op == OP_CHANGE_VAO)
-        continue;
-
-      OP_PRINT ("Op %u: %u", i, op->op);
-
-      switch (op->op)
-        {
-        case OP_CHANGE_PROJECTION:
-          graphene_matrix_to_float (&op->projection, mat);
-          glUniformMatrix4fv (program->projection_location, 1, GL_FALSE, mat);
-          OP_PRINT (" -> Projection");
-          /*graphene_matrix_print (&op->projection);*/
-          break;
-
-        case OP_CHANGE_MODELVIEW:
-          graphene_matrix_to_float (&op->modelview, mat);
-          glUniformMatrix4fv (program->modelview_location, 1, GL_FALSE, mat);
-          OP_PRINT (" -> Modelview");
-          /*graphene_matrix_print (&op->modelview);*/
-          break;
-
-        case OP_CHANGE_PROGRAM:
-          program = op->program;
-          glUseProgram (op->program->id);
-          OP_PRINT (" -> Program: %d", op->program->id);
-          break;
-
-        case OP_CHANGE_RENDER_TARGET:
-          OP_PRINT (" -> Render Target: %d", op->render_target_id);
-
-          glBindFramebuffer (GL_FRAMEBUFFER, op->render_target_id);
-          if (op->render_target_id != 0)
-            {
-              glDisable (GL_SCISSOR_TEST);
-              glClearColor (0.0, 0.0, 0.0, 0.0);
-              glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-            }
-          else
-            {
-              /* Reset glScissor, etc. */
-              gsk_gl_renderer_setup_render_mode (self);
-            }
-          break;
-
-        case OP_CHANGE_VIEWPORT:
-          OP_PRINT (" -> New Viewport: %f, %f, %f, %f", op->viewport.origin.x, op->viewport.origin.y, op->viewport.size.width, op->viewport.size.height);
-          glUniform4f (program->viewport_location,
-                       op->viewport.origin.x, op->viewport.origin.y,
-                       op->viewport.size.width, op->viewport.size.height);
-          glViewport (0, 0, op->viewport.size.width, op->viewport.size.height);
-          break;
-
-        case OP_CHANGE_OPACITY:
-          glUniform1f (program->alpha_location, op->opacity);
-          break;
-
-        case OP_CHANGE_COLOR:
-          OP_PRINT (" -> Color: (%f, %f, %f, %f)", op->color.red, op->color.green, op->color.blue, op->color.alpha);
-          g_assert (program == &self->color_program || program == &self->coloring_program);
-          glUniform4f (program->color_location,
-                       op->color.red, op->color.green, op->color.blue, op->color.alpha);
-          break;
-
-        case OP_CHANGE_CLIP:
-          OP_PRINT (" -> Clip");
-          glUniform4f (program->clip_location,
-                       op->clip.bounds.origin.x, op->clip.bounds.origin.y,
-                       op->clip.bounds.size.width, op->clip.bounds.size.height);
-
-          glUniform4f (program->clip_corner_widths_location,
-                       MAX (op->clip.corner[0].width, 1),
-                       MAX (op->clip.corner[1].width, 1),
-                       MAX (op->clip.corner[2].width, 1),
-                       MAX (op->clip.corner[3].width, 1));
-          glUniform4f (program->clip_corner_heights_location,
-                       MAX (op->clip.corner[0].height, 1),
-                       MAX (op->clip.corner[1].height, 1),
-                       MAX (op->clip.corner[2].height, 1),
-                       MAX (op->clip.corner[3].height, 1));
-          break;
-
-        case OP_CHANGE_SOURCE_TEXTURE:
-          g_assert(op->texture_id != 0);
-          OP_PRINT (" -> New texture: %d", op->texture_id);
-          /* Use texture unit 0 for the source */
-          glUniform1i (program->source_location, 0);
-          glActiveTexture (GL_TEXTURE0);
-          glBindTexture (GL_TEXTURE_2D, op->texture_id);
-
-          break;
-
-        case OP_CHANGE_LINEAR_GRADIENT:
-            OP_PRINT (" -> Linear gradient");
-            glUniform1i (program->n_color_stops_location,
-                         op->linear_gradient.n_color_stops);
-            glUniform4fv (program->color_stops_location,
-                          op->linear_gradient.n_color_stops,
-                          op->linear_gradient.color_stops);
-            glUniform1fv (program->color_offsets_location,
-                          op->linear_gradient.n_color_stops,
-                          op->linear_gradient.color_offsets);
-            glUniform2f (program->start_point_location,
-                         op->linear_gradient.start_point.x, op->linear_gradient.start_point.y);
-            glUniform2f (program->end_point_location,
-                         op->linear_gradient.end_point.x, op->linear_gradient.end_point.y);
-          break;
-
-        case OP_DRAW:
-          OP_PRINT (" -> draw %ld\n", op->vao_offset);
-          glDrawArrays (GL_TRIANGLES, op->vao_offset, N_VERTICES);
-          break;
-
-        default:
-          g_warn_if_reached ();
-        }
-
-      OP_PRINT ("\n");
-    }
-
-  /* Done drawing, destroy the buffer again.
-   * TODO: Can we reuse the memory, though? */
-  g_free (vertex_data);
-}
-
-static void
-gsk_gl_renderer_do_render (GskRenderer           *renderer,
-                           GskRenderNode         *root,
-                           const graphene_rect_t *viewport,
-                           int                    scale_factor)
-{
-  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-  RenderOpBuilder render_op_builder;
-  graphene_matrix_t modelview, projection;
-#ifdef G_ENABLE_DEBUG
-  GskProfiler *profiler;
-  gint64 gpu_time, cpu_time;
-#endif
-
-#ifdef G_ENABLE_DEBUG
-  profiler = gsk_renderer_get_profiler (renderer);
-#endif
-
-  if (self->gl_context == NULL)
-    {
-      GSK_NOTE (OPENGL, g_print ("No valid GL context associated to the renderer"));
-      return;
-    }
-
-  self->viewport = *viewport;
-
-  /* Set up the modelview and projection matrices to fit our viewport */
-  graphene_matrix_init_scale (&modelview, scale_factor, scale_factor, 1.0);
-  graphene_matrix_init_ortho (&projection,
-                              viewport->origin.x,
-                              viewport->origin.x + viewport->size.width,
-                              viewport->origin.y,
-                              viewport->origin.y + viewport->size.height,
-                              ORTHO_NEAR_PLANE,
-                              ORTHO_FAR_PLANE);
-
-  if (self->texture_id == 0)
-    graphene_matrix_scale (&projection, 1, -1, 1);
-
-  gsk_gl_driver_begin_frame (self->gl_driver);
-  gsk_gl_glyph_cache_begin_frame (&self->glyph_cache);
-
-  memset (&render_op_builder, 0, sizeof (render_op_builder));
-  render_op_builder.renderer = self;
-  render_op_builder.current_projection = projection;
-  render_op_builder.current_modelview = modelview;
-  render_op_builder.current_viewport = *viewport;
-  render_op_builder.current_render_target = self->texture_id;
-  render_op_builder.current_opacity = 1.0f;
-  gsk_rounded_rect_init_from_rect (&render_op_builder.current_clip, &self->viewport, 0.0f);
-  gsk_gl_renderer_add_render_ops (self, root, &render_op_builder);
-
-  /*g_message ("Ops: %u", self->render_ops->len);*/
-
-  /* Now actually draw things... */
-#ifdef G_ENABLE_DEBUG
-  gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
-  gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
-#endif
-
-  gsk_gl_renderer_resize_viewport (self, viewport);
-  gsk_gl_renderer_setup_render_mode (self);
-  gsk_gl_renderer_clear (self);
-
-  glEnable (GL_DEPTH_TEST);
-  glDepthFunc (GL_LEQUAL);
-
-  /* Pre-multiplied alpha! */
-  glEnable (GL_BLEND);
-  glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-  glBlendEquation (GL_FUNC_ADD);
-
-  gsk_gl_renderer_render_ops (self, render_op_builder.buffer_size);
-
-  gsk_gl_driver_end_frame (self->gl_driver);
-
-#ifdef G_ENABLE_DEBUG
-  gsk_profiler_counter_inc (profiler, self->profile_counters.frames);
-
-  cpu_time = gsk_profiler_timer_end (profiler, self->profile_timers.cpu_time);
-  gsk_profiler_timer_set (profiler, self->profile_timers.cpu_time, cpu_time);
-
-  gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler);
-  gsk_profiler_timer_set (profiler, self->profile_timers.gpu_time, gpu_time);
-
-  gsk_profiler_push_samples (profiler);
-#endif
-}
-
-static GdkTexture *
-gsk_gl_renderer_render_texture (GskRenderer           *renderer,
-                                GskRenderNode         *root,
-                                const graphene_rect_t *viewport)
-{
-  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-  GdkTexture *texture;
-  int stride;
-  guchar *data;
-  int width, height;
-
-  g_return_val_if_fail (self->gl_context != NULL, NULL);
-
-  self->render_mode = RENDER_FULL;
-  width = ceilf (viewport->size.width);
-  height = ceilf (viewport->size.height);
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  /* Prepare our framebuffer */
-  gsk_gl_driver_begin_frame (self->gl_driver);
-  gsk_gl_renderer_create_buffers (self, width, height, 1);
-  gsk_gl_renderer_clear (self);
-  gsk_gl_driver_end_frame (self->gl_driver);
-
-  /* Render the actual scene */
-  gsk_gl_renderer_do_render (renderer, root, viewport, 1);
-
-  /* Prepare memory for the glReadPixels call */
-  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
-  data = g_malloc (height * stride);
-
-  /* Bind our framebuffer again and read from it */
-  gsk_gl_driver_begin_frame (self->gl_driver);
-  gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id);
-  glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
-  gsk_gl_driver_end_frame (self->gl_driver);
-
-  /* Create texture from the downloaded data */
-  texture = gdk_texture_new_for_data (data, width, height, stride);
-
-  return texture;
-}
-
-static void
-gsk_gl_renderer_render (GskRenderer   *renderer,
-                        GskRenderNode *root)
-{
-  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
-  GdkWindow *window = gsk_renderer_get_window (renderer);
-  graphene_rect_t viewport;
-
-  if (self->gl_context == NULL)
-    return;
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  viewport.origin.x = 0;
-  viewport.origin.y = 0;
-  viewport.size.width = gdk_window_get_width (window) * self->scale_factor;
-  viewport.size.height = gdk_window_get_height (window) * self->scale_factor;
-
-  gsk_gl_renderer_do_render (renderer, root, &viewport, self->scale_factor);
-
-  gdk_gl_context_make_current (self->gl_context);
-  gsk_gl_renderer_clear_tree (self);
-  gsk_gl_renderer_destroy_buffers (self);
-}
-
-static void
-gsk_gl_renderer_class_init (GskGLRendererClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-  GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
-
-  gobject_class->dispose = gsk_gl_renderer_dispose;
-
-  renderer_class->realize = gsk_gl_renderer_realize;
-  renderer_class->unrealize = gsk_gl_renderer_unrealize;
-  renderer_class->begin_draw_frame = gsk_gl_renderer_begin_draw_frame;
-  renderer_class->render = gsk_gl_renderer_render;
-  renderer_class->render_texture = gsk_gl_renderer_render_texture;
-}
-
-static void
-gsk_gl_renderer_init (GskGLRenderer *self)
-{
-  gsk_ensure_resources ();
-
-
-  self->scale_factor = 1;
-  self->render_ops = g_array_new (TRUE, FALSE, sizeof (RenderOp));
-
-#ifdef G_ENABLE_DEBUG
-  {
-    GskProfiler *profiler = gsk_renderer_get_profiler (GSK_RENDERER (self));
-
-    self->profile_counters.frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE);
-    self->profile_counters.draw_calls = gsk_profiler_add_counter (profiler, "draws", "glDrawArrays", TRUE);
-
-    self->profile_timers.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU time", FALSE, TRUE);
-    self->profile_timers.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU time", FALSE, TRUE);
-  }
-#endif
-}
diff --git a/gsk/gskglrendererprivate.h b/gsk/gskglrendererprivate.h
deleted file mode 100644 (file)
index a30b201..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef __GSK_GL_RENDERER_PRIVATE_H__
-#define __GSK_GL_RENDERER_PRIVATE_H__
-
-#include <gsk/gskrenderer.h>
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_RENDERER (gsk_gl_renderer_get_type ())
-
-#define GSK_GL_RENDERER(obj)                    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_RENDERER, GskGLRenderer))
-#define GSK_IS_GL_RENDERER(obj)                 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_RENDERER))
-#define GSK_GL_RENDERER_CLASS(klass)            (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
-#define GSK_IS_GL_RENDERER_CLASS(klass)         (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_RENDERER))
-#define GSK_GL_RENDERER_GET_CLASS(obj)          (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
-
-typedef struct _GskGLRenderer                   GskGLRenderer;
-typedef struct _GskGLRendererClass              GskGLRendererClass;
-
-GType gsk_gl_renderer_get_type (void) G_GNUC_CONST;
-
-G_END_DECLS
-
-#endif /* __GSK_GL_RENDERER_PRIVATE_H__ */
index f40a37869e6fe3c0f0840041908b476ef4874b87..9bdfd6a26a25d8977eefd160ca4cab2bdf226fa3 100644 (file)
@@ -39,7 +39,7 @@
 
 #include "gskcairorendererprivate.h"
 #include "gskdebugprivate.h"
-#include "gskglrendererprivate.h"
+#include "gl/gskglrendererprivate.h"
 #include "gskprofilerprivate.h"
 #include "gskrendernodeprivate.h"
 
index 7f2ebcce705343f65b20da54df922d01abc643a2..89de8bb5f5e662a9ae6cbb9e52594abb494f4b7b 100644 (file)
@@ -26,14 +26,14 @@ gsk_private_sources = files([
   'gskcairoblur.c',
   'gskcairorenderer.c',
   'gskdebug.c',
-  'gskgldriver.c',
-  'gskglprofiler.c',
-  'gskglrenderer.c',
-  'gskglglyphcache.c',
-  'gskglimage.c',
   'gskprivate.c',
   'gskprofiler.c',
   'gskshaderbuilder.c',
+  'gl/gskglprofiler.c',
+  'gl/gskglrenderer.c',
+  'gl/gskglglyphcache.c',
+  'gl/gskglimage.c',
+  'gl/gskgldriver.c',
 ])
 
 gsk_public_headers = files([